Skip to content

SAAD: GitOps for DDM assets#49046

Draft
MagnusHJensen wants to merge 19 commits into
48568-asset-api-ddm-syncfrom
48570-ddm-assets-gitops
Draft

SAAD: GitOps for DDM assets#49046
MagnusHJensen wants to merge 19 commits into
48568-asset-api-ddm-syncfrom
48570-ddm-assets-gitops

Conversation

@MagnusHJensen

@MagnusHJensen MagnusHJensen commented Jul 9, 2026

Copy link
Copy Markdown
Member

Related issue: Resolves #48570

Checklist for submitter

If some of the following don't apply, delete the relevant line.

  • Changes file added for user-visible changes in changes/, orbit/changes/ or ee/fleetd-chrome/changes.
    See Changes files for more information. Added in a previous PR

  • Input data is properly validated, SELECT * is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters.

  • Timeouts are implemented and retries are limited to avoid infinite loops

  • If paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes

Testing

For unreleased bug fixes in a release candidate, one of:

  • Confirmed that the fix is not expected to adversely impact load test results
  • Alerted the release DRI if additional load testing is needed

Database migrations

  • Checked schema for all modified table for columns that will auto-update timestamps during migration.
  • Confirmed that updating the timestamps is acceptable, and will not cause unwanted side effects.
  • Ensured the correct collation is explicitly set for character columns (COLLATE utf8mb4_unicode_ci).

New Fleet configuration settings

  • Setting(s) is/are explicitly excluded from GitOps

If you didn't check the box above, follow this checklist for GitOps-enabled settings:

  • Verified that the setting is exported via fleetctl generate-gitops
  • Verified the setting is documented in a separate PR to the GitOps documentation
  • Verified that the setting is cleared on the server if it is not supplied in a YAML file (or that it is documented as being optional)
  • Verified that any relevant UI is disabled when GitOps mode is enabled

fleetd/orbit/Fleet Desktop

  • Verified compatibility with the latest released version of Fleet (see Must rule)
  • If the change applies to only one platform, confirmed that runtime.GOOS is used as needed to isolate changes
  • Verified that fleetd runs on macOS, Linux and Windows
  • Verified auto-update works from the released version of component to the new version (see tools/tuf/test)

Summary by CodeRabbit

  • New Features
    • Added end-to-end Apple DDM asset support in GitOps, including export and GitOps parsing for macOS settings assets.
    • Introduced Apple DDM asset management APIs (list/get/download/create/delete) plus a batch set operation with dry-run.
  • Bug Fixes
    • Improved Apple MDM/DDM reconciliation so referenced asset updates trigger re-delivery via asset-aware tokening.
    • Added safer validation around asset type changes and deletion conflicts when assets are still referenced.
  • Tests
    • Expanded unit and integration coverage for asset parsing, upload/apply behavior, reconciliation, and access control.

@MagnusHJensen

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@MagnusHJensen MagnusHJensen changed the base branch from main to 48568-asset-api-ddm-sync July 9, 2026 16:15
@MagnusHJensen MagnusHJensen removed the request for review from Copilot July 9, 2026 16:16
@MagnusHJensen

Copy link
Copy Markdown
Member Author

@coderabbitai stop review

@MagnusHJensen

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

  • Copilot's review of this pull request may be incomplete because some of the changed files are excluded by your Copilot content exclusion settings. See Excluding content from Copilot for details.

Pull request overview

Adds GitOps + API support for Apple Declarative Device Management (DDM) assets, including server-side asset CRUD/batch endpoints, GitOps parsing/application ordering, and reconciliation/token changes so asset edits trigger host resyncs even when declarations are unchanged.

Changes:

  • Introduces Apple DDM asset API endpoints (/fleet/assets + batch) with authz, datastore support, and fleetctl client methods.
  • Extends DDM reconciliation/tokening to incorporate assets_updated_at (and serves referenced assets via the DDM “declaration-items”/“declaration/asset/…” paths).
  • Updates GitOps parsing and fleetctl generate-gitops to round-trip controls.apple_settings.assets.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
server/service/mdm.go Batch profile upload now resolves and attaches declaration→asset reference UUIDs.
server/service/integration_mdm_profiles_test.go Updates expected error message for forbidden declaration types in batch endpoint.
server/service/integration_mdm_ddm_test.go Adds integration coverage for asset-only updates triggering DDM resync + token changes; updates EffectiveDDMToken call sites.
server/service/integration_core_test.go Updates NewMDMAppleDeclaration call signature in integration test.
server/service/handler.go Registers new /fleet/assets CRUD + batch routes under MDM middleware.
server/service/client.go Applies assets before declarations/profiles in GitOps; adds local asset file loading helper + asset spec extraction.
server/service/client_teams.go Adds GitOps client helper to batch-set assets for a team.
server/service/client_profiles.go Adds client methods to list/download DDM assets (for generate-gitops).
server/service/apple_mdm.go Adds asset reference discovery for declarations; adds FOSS stubs for asset endpoints; serves DDM asset declarations to devices.
server/service/apple_mdm_test.go Updates service unit tests for new declaration signature + asset reference validation.
server/service/apple_mdm_ddm_test.go Updates declaration creation calls for new signature.
server/mock/service/service_mock.go Extends service mock with DDM asset API methods.
server/mock/datastore_mock.go Extends datastore mock with asset CRUD/batch/reference methods and new declaration signature.
server/mdm/apple/reconcile.go Treats assets_updated_at as a resync trigger and stamps it into host declaration rows.
server/mdm/apple/reconcile_test.go Adds unit coverage for assets_updated_at-driven deltas.
server/fleet/service.go Extends Service interface with Apple DDM asset methods.
server/fleet/request.go Adds max request size constant for asset uploads.
server/fleet/datastore.go Extends Datastore interface with asset CRUD/batch/reference methods; updates declaration signature.
server/fleet/apple_mdm.go Adds assets_updated_at plumbing, declaration asset reference UUIDs, EffectiveDDMToken update, and asset-related types.
server/fleet/apple_mdm_test.go Adds/updates tests for forbidden type messaging, host declaration equality, and EffectiveDDMToken hashing order.
server/fleet/app.go Adds macos_settings.assets field for GitOps parsing (not persisted in AppConfig).
server/datastore/mysql/teams_test.go Updates declaration creation calls for new signature.
server/datastore/mysql/secret_variables_test.go Updates declaration creation calls for new signature.
server/datastore/mysql/labels_test.go Updates declaration creation calls for new signature.
server/datastore/mysql/hosts_test.go Updates declaration creation calls for new signature.
server/datastore/mysql/apple_mdm.go Persists declaration↔asset refs, updates DDM token SQL for assets_updated_at, adds asset CRUD/batch/reference queries, and serves assets to devices.
server/datastore/mysql/apple_mdm_test.go Updates datastore tests for new declaration signature.
server/datastore/mysql/apple_mdm_ddm_test.go Adds datastore tests for asset CRUD/batch and assets_updated_at token behavior; updates declaration signature call sites.
server/datastore/mysql/apple_mdm_batched.go Loads MAX(uploaded_at) across referenced assets for reconcile and persists assets_updated_at in host declaration rows.
server/authz/policy.rego Adds ddm_asset authz rules for global/team roles.
server/authz/policy_test.go Adds coverage for ddm_asset read/write authorization matrix.
pkg/spec/gitops.go Parses/expands controls.apple_settings.assets like profiles (path resolution + secret collection).
pkg/spec/gitops_test.go Adds test ensuring assets paths are resolved and secrets are collected.
ee/server/service/apple_mdm.go Implements DDM asset endpoints (authz, validation, batch-set semantics) in EE.
ee/server/service/apple_mdm_test.go Adds EE service tests for asset CRUD/batch behaviors and authz.
cmd/fleetctl/fleetctl/gitops_test.go Updates datastore mock signature for NewMDMAppleDeclaration in fleetctl GitOps tests.
cmd/fleetctl/fleetctl/generate_gitops.go Emits assets: list and writes asset JSON files during generate-gitops.
cmd/fleetctl/fleetctl/generate_gitops_test.go Adds coverage for generate-gitops emitting/writing assets.
changes/38986-support-ddm-assets User-visible changes entry (content excluded from review by policy).
Files excluded by content exclusion policy (1)
  • changes/38986-support-ddm-assets
Comments suppressed due to low confidence (1)

server/datastore/mysql/apple_mdm.go:8162

  • GetAppleDDMAssetForDelivery joins host_mdm_apple_declarations without restricting operation_type. That means an asset could still be retrievable for a host even if the host currently only has a pending/verified REMOVE row for the referencing declaration (i.e., it’s no longer meant to be delivered/advertised).

To match the intent (“current declarations”) and avoid serving assets for removed declarations, filter to install operations in the join/where clause.

	err := sqlx.GetContext(ctx, ds.reader(ctx), &asset, `SELECT a.asset_uuid, a.team_id, a.identifier, a.name, a.token, a.created_at, a.uploaded_at, a.raw_json
		FROM
			hosts h
			JOIN host_mdm_apple_declarations hmad ON hmad.host_uuid = h.uuid
			JOIN mdm_apple_declaration_asset_references r ON r.declaration_uuid = hmad.declaration_uuid
			JOIN mdm_apple_declaration_assets a ON a.asset_uuid = r.asset_uuid
			AND a.team_id = COALESCE(h.team_id, 0)
		WHERE
			h.uuid = ?
			AND a.identifier = ?`, hostUUID, identifier)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread server/service/mdm.go
Comment on lines +2359 to +2370
// Resolve DDM asset references for declarations so the batch path links each
// declaration to the assets it references (mirroring the single-upload
// path). GitOps applies assets before declarations, so referenced assets
// already exist by this point.
for _, d := range appleDeclsSlice {
d.TeamID = tmID
assetRefs, err := svc.handleDeclarationAssetReferences(ctx, d)
if err != nil {
return ctxerr.Wrap(ctx, err, "handling declaration asset references")
}
d.AssetReferenceUUIDs = assetRefs
}
Comment thread server/service/client.go
Comment thread cmd/fleetctl/fleetctl/generate_gitops.go
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This change adds Apple DDM asset support across the stack. It introduces new asset types and interface methods, updates datastore persistence and reconciliation to track asset references and freshness, adds service and HTTP endpoints for listing, fetching, creating, deleting, and batch-setting assets, extends GitOps parsing and generate-gitops to handle controls.apple_settings.assets, and updates client apply flows to apply asset files before profiles and declarations. It also adds authorization rules and tests for the new asset paths.

Possibly related PRs

  • fleetdm/fleet#48866: Adds the underlying asset reference tables and columns used by the new asset persistence and reconciliation paths.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and clearly describes the main change: GitOps support for DDM assets.
Description check ✅ Passed The description matches the repo template and includes the required issue, checklist, testing, migration, and GitOps sections.
Linked Issues check ✅ Passed The changes cover DDM asset parsing, generate-gitops output, in-place type checks, referenced-asset deletion conflicts, and PayloadScope handling.
Out of Scope Changes check ✅ Passed The diff appears focused on the linked DDM assets and PayloadScope work, with tests, mocks, auth, and changelog support all in scope.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 48570-ddm-assets-gitops
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch 48570-ddm-assets-gitops

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.44.1)
server/service/integration_core_test.go

ast-grep timed out on this file


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
server/datastore/mysql/apple_mdm_batched.go (1)

888-923: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Minor: args capacity hint is stale.

args := make([]any, 0, len(batch)*10) still assumes 10 args per row, but each row now appends 11 values (assets_updated_at added). Not a correctness issue — just an extra reallocation per batch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/datastore/mysql/apple_mdm_batched.go` around lines 888 - 923, In the
batched declaration insert logic, the args slice capacity hint is outdated
because each row now appends 11 values in the loop that builds `valueParts` and
`args`. Update the `args := make([]any, 0, len(batch)*10)` allocation in the
`host_mdm_apple_declarations` batch write path to match the current per-row
argument count so the `batch` processing in this function avoids unnecessary
reallocations.
server/datastore/mysql/apple_mdm_ddm_test.go (1)

700-832: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

No test coverage for channel/scope isolation of asset delivery.

testGetAppleDDMAssetForDelivery thoroughly covers team scoping (global vs. team, cross-team leakage, unknown host) but doesn't exercise device- vs. user-channel (PayloadScope) isolation, unlike testDDMChannelScopeIsolation which does this for declarations. Given GetAppleDDMAssetForDelivery(ctx, identifier, hostUUID) takes no scope parameter (see server/service/apple_mdm.go's handleDeclarationAsset, which also doesn't receive/forward scope), it's worth confirming assets referenced only by a user-scoped declaration aren't deliverable via the device channel and vice versa.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/datastore/mysql/apple_mdm_ddm_test.go` around lines 700 - 832,
testGetAppleDDMAssetForDelivery only covers team scoping and cross-team leakage,
but not device-vs-user PayloadScope isolation for asset delivery. Add coverage
that exercises GetAppleDDMAssetForDelivery with declarations tied to both device
and user channels, verifying assets referenced only by a user-scoped declaration
are not returned on the device channel and vice versa. Use the existing helpers
around handleDeclarationAsset and align the new assertions with the behavior
already validated in testDDMChannelScopeIsolation for declarations.
server/datastore/mysql/apple_mdm.go (2)

5016-5030: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Asset reference insert uses declUUID before the post-upsert reload.

The assetReferences insert (using the locally-generated declUUID) happens before reloadStmt re-reads the actual persisted declaration_uuid at line 5031. Currently this is safe because assetReferences is only ever non-nil via NewMDMAppleDeclaration (an insert-only path that errors on conflict rather than updating), so the pre- and post-reload UUIDs are identical. But SetOrUpdateMDMAppleDeclaration's ON DUPLICATE KEY UPDATE path does not update declaration_uuid, so if assetReferences were ever passed non-nil through that path, this ordering would silently associate assets with the wrong (freshly-generated, unused) UUID instead of the actual existing declaration row.

Moving the assetReferences insert after the reload (like the labels/variables associations already do) would remove this footgun.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/datastore/mysql/apple_mdm.go` around lines 5016 - 5030, The asset
reference insert in SetOrUpdateMDMAppleDeclaration uses the locally generated
declUUID before reloadStmt re-reads the persisted declaration UUID, which is a
footgun for the ON DUPLICATE KEY UPDATE path. Move the
mdm_apple_declaration_asset_references insert to after the reload step, matching
the labels/variables handling, so asset references always use the actual
persisted declaration_uuid. Keep the change scoped around
SetOrUpdateMDMAppleDeclaration and the assetReferences insert block.

4587-4649: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

N+1 per-declaration round trips for asset association reconciliation.

updateDeclarationsAssetAssociations runs a DELETE (and often an INSERT) per declaration in the incoming batch, inside the same transaction. For GitOps batch applies with many declarations this means O(n) sequential round trips even for declarations whose asset references haven't changed, adding latency to BatchSetMDMProfiles.

Consider batching the delete/insert across all declarations in one or two statements (e.g., delete stale rows for all declaration UUIDs in one query using sqlx.In with a list of decl UUIDs, and bulk-insert all (declaration_uuid, asset_uuid) pairs across declarations in a single multi-row INSERT).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/datastore/mysql/apple_mdm.go` around lines 4587 - 4649,
updateDeclarationsAssetAssociations is doing per-declaration DELETE/INSERT round
trips, causing an N+1 pattern during batch reconciliation. Refactor the loop in
updateDeclarationsAssetAssociations to batch stale-row deletion for all
declaration UUIDs at once using sqlx.In, and similarly bulk-insert all
declaration_uuid/asset_uuid pairs in one multi-row INSERT. Keep the existing
reconciliation behavior and updatedDB tracking, but avoid issuing one statement
per declaration.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ee/server/service/apple_mdm.go`:
- Around line 233-256: The team lookup failure paths in BatchSetAppleDDMAssets
are returning before authz is marked as skipped, which causes authzcheck to flag
them as missed checks. Move the svc.authz.SkipAuthorization(ctx) call so it runs
before returning any team-resolution error or not-found result in the
BatchSetAppleDDMAssets flow, while keeping the existing team_id/team_name
validation and later Authorize call unchanged.
- Around line 164-206: The DDM asset validation path in validateAppleDDMAsset
currently ignores the timestamp returned by ExpandEmbeddedSecretsAndUpdatedAt,
so secret rotations do not affect the stored asset token. Update the MDM Apple
declaration asset model and token generation flow to persist and use
secrets_updated_at on mdm_apple_declaration_assets, mirroring the behavior used
by MDMAppleDeclaration, and ensure the DDM asset token includes that value so
changes to referenced $FLEET_SECRET_* entries trigger re-delivery.

In `@server/datastore/mysql/apple_mdm.go`:
- Around line 8145-8170: The query in GetAppleDDMAssetForDelivery is missing the
same operation_type scoping used by sibling declaration-delivery paths, so it
can return assets from remove declarations. Update the SQL joined through
host_mdm_apple_declarations/MDMAppleDDMDeclarationsResponse-style logic to
filter for the install operation only, using the appropriate operation_type
condition alongside hostUUID and identifier so the delivered asset matches the
host’s currently applicable declaration state.

---

Nitpick comments:
In `@server/datastore/mysql/apple_mdm_batched.go`:
- Around line 888-923: In the batched declaration insert logic, the args slice
capacity hint is outdated because each row now appends 11 values in the loop
that builds `valueParts` and `args`. Update the `args := make([]any, 0,
len(batch)*10)` allocation in the `host_mdm_apple_declarations` batch write path
to match the current per-row argument count so the `batch` processing in this
function avoids unnecessary reallocations.

In `@server/datastore/mysql/apple_mdm_ddm_test.go`:
- Around line 700-832: testGetAppleDDMAssetForDelivery only covers team scoping
and cross-team leakage, but not device-vs-user PayloadScope isolation for asset
delivery. Add coverage that exercises GetAppleDDMAssetForDelivery with
declarations tied to both device and user channels, verifying assets referenced
only by a user-scoped declaration are not returned on the device channel and
vice versa. Use the existing helpers around handleDeclarationAsset and align the
new assertions with the behavior already validated in
testDDMChannelScopeIsolation for declarations.

In `@server/datastore/mysql/apple_mdm.go`:
- Around line 5016-5030: The asset reference insert in
SetOrUpdateMDMAppleDeclaration uses the locally generated declUUID before
reloadStmt re-reads the persisted declaration UUID, which is a footgun for the
ON DUPLICATE KEY UPDATE path. Move the mdm_apple_declaration_asset_references
insert to after the reload step, matching the labels/variables handling, so
asset references always use the actual persisted declaration_uuid. Keep the
change scoped around SetOrUpdateMDMAppleDeclaration and the assetReferences
insert block.
- Around line 4587-4649: updateDeclarationsAssetAssociations is doing
per-declaration DELETE/INSERT round trips, causing an N+1 pattern during batch
reconciliation. Refactor the loop in updateDeclarationsAssetAssociations to
batch stale-row deletion for all declaration UUIDs at once using sqlx.In, and
similarly bulk-insert all declaration_uuid/asset_uuid pairs in one multi-row
INSERT. Keep the existing reconciliation behavior and updatedDB tracking, but
avoid issuing one statement per declaration.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1565b6fb-4583-44af-8453-b2683abf2b14

📥 Commits

Reviewing files that changed from the base of the PR and between 56a3c75 and 28ff257.

📒 Files selected for processing (39)
  • changes/38986-support-ddm-assets
  • cmd/fleetctl/fleetctl/generate_gitops.go
  • cmd/fleetctl/fleetctl/generate_gitops_test.go
  • cmd/fleetctl/fleetctl/gitops_test.go
  • ee/server/service/apple_mdm.go
  • ee/server/service/apple_mdm_test.go
  • pkg/spec/gitops.go
  • pkg/spec/gitops_test.go
  • server/authz/policy.rego
  • server/authz/policy_test.go
  • server/datastore/mysql/apple_mdm.go
  • server/datastore/mysql/apple_mdm_batched.go
  • server/datastore/mysql/apple_mdm_ddm_test.go
  • server/datastore/mysql/apple_mdm_test.go
  • server/datastore/mysql/hosts_test.go
  • server/datastore/mysql/labels_test.go
  • server/datastore/mysql/secret_variables_test.go
  • server/datastore/mysql/teams_test.go
  • server/fleet/app.go
  • server/fleet/apple_mdm.go
  • server/fleet/apple_mdm_test.go
  • server/fleet/datastore.go
  • server/fleet/request.go
  • server/fleet/service.go
  • server/mdm/apple/reconcile.go
  • server/mdm/apple/reconcile_test.go
  • server/mock/datastore_mock.go
  • server/mock/service/service_mock.go
  • server/service/apple_mdm.go
  • server/service/apple_mdm_ddm_test.go
  • server/service/apple_mdm_test.go
  • server/service/client.go
  • server/service/client_profiles.go
  • server/service/client_teams.go
  • server/service/handler.go
  • server/service/integration_core_test.go
  • server/service/integration_mdm_ddm_test.go
  • server/service/integration_mdm_profiles_test.go
  • server/service/mdm.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 3

🧹 Nitpick comments (4)
server/datastore/mysql/apple_mdm_batched.go (1)

888-923: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Minor: args capacity hint is stale.

args := make([]any, 0, len(batch)*10) still assumes 10 args per row, but each row now appends 11 values (assets_updated_at added). Not a correctness issue — just an extra reallocation per batch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/datastore/mysql/apple_mdm_batched.go` around lines 888 - 923, In the
batched declaration insert logic, the args slice capacity hint is outdated
because each row now appends 11 values in the loop that builds `valueParts` and
`args`. Update the `args := make([]any, 0, len(batch)*10)` allocation in the
`host_mdm_apple_declarations` batch write path to match the current per-row
argument count so the `batch` processing in this function avoids unnecessary
reallocations.
server/datastore/mysql/apple_mdm_ddm_test.go (1)

700-832: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

No test coverage for channel/scope isolation of asset delivery.

testGetAppleDDMAssetForDelivery thoroughly covers team scoping (global vs. team, cross-team leakage, unknown host) but doesn't exercise device- vs. user-channel (PayloadScope) isolation, unlike testDDMChannelScopeIsolation which does this for declarations. Given GetAppleDDMAssetForDelivery(ctx, identifier, hostUUID) takes no scope parameter (see server/service/apple_mdm.go's handleDeclarationAsset, which also doesn't receive/forward scope), it's worth confirming assets referenced only by a user-scoped declaration aren't deliverable via the device channel and vice versa.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/datastore/mysql/apple_mdm_ddm_test.go` around lines 700 - 832,
testGetAppleDDMAssetForDelivery only covers team scoping and cross-team leakage,
but not device-vs-user PayloadScope isolation for asset delivery. Add coverage
that exercises GetAppleDDMAssetForDelivery with declarations tied to both device
and user channels, verifying assets referenced only by a user-scoped declaration
are not returned on the device channel and vice versa. Use the existing helpers
around handleDeclarationAsset and align the new assertions with the behavior
already validated in testDDMChannelScopeIsolation for declarations.
server/datastore/mysql/apple_mdm.go (2)

5016-5030: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Asset reference insert uses declUUID before the post-upsert reload.

The assetReferences insert (using the locally-generated declUUID) happens before reloadStmt re-reads the actual persisted declaration_uuid at line 5031. Currently this is safe because assetReferences is only ever non-nil via NewMDMAppleDeclaration (an insert-only path that errors on conflict rather than updating), so the pre- and post-reload UUIDs are identical. But SetOrUpdateMDMAppleDeclaration's ON DUPLICATE KEY UPDATE path does not update declaration_uuid, so if assetReferences were ever passed non-nil through that path, this ordering would silently associate assets with the wrong (freshly-generated, unused) UUID instead of the actual existing declaration row.

Moving the assetReferences insert after the reload (like the labels/variables associations already do) would remove this footgun.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/datastore/mysql/apple_mdm.go` around lines 5016 - 5030, The asset
reference insert in SetOrUpdateMDMAppleDeclaration uses the locally generated
declUUID before reloadStmt re-reads the persisted declaration UUID, which is a
footgun for the ON DUPLICATE KEY UPDATE path. Move the
mdm_apple_declaration_asset_references insert to after the reload step, matching
the labels/variables handling, so asset references always use the actual
persisted declaration_uuid. Keep the change scoped around
SetOrUpdateMDMAppleDeclaration and the assetReferences insert block.

4587-4649: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

N+1 per-declaration round trips for asset association reconciliation.

updateDeclarationsAssetAssociations runs a DELETE (and often an INSERT) per declaration in the incoming batch, inside the same transaction. For GitOps batch applies with many declarations this means O(n) sequential round trips even for declarations whose asset references haven't changed, adding latency to BatchSetMDMProfiles.

Consider batching the delete/insert across all declarations in one or two statements (e.g., delete stale rows for all declaration UUIDs in one query using sqlx.In with a list of decl UUIDs, and bulk-insert all (declaration_uuid, asset_uuid) pairs across declarations in a single multi-row INSERT).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/datastore/mysql/apple_mdm.go` around lines 4587 - 4649,
updateDeclarationsAssetAssociations is doing per-declaration DELETE/INSERT round
trips, causing an N+1 pattern during batch reconciliation. Refactor the loop in
updateDeclarationsAssetAssociations to batch stale-row deletion for all
declaration UUIDs at once using sqlx.In, and similarly bulk-insert all
declaration_uuid/asset_uuid pairs in one multi-row INSERT. Keep the existing
reconciliation behavior and updatedDB tracking, but avoid issuing one statement
per declaration.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ee/server/service/apple_mdm.go`:
- Around line 233-256: The team lookup failure paths in BatchSetAppleDDMAssets
are returning before authz is marked as skipped, which causes authzcheck to flag
them as missed checks. Move the svc.authz.SkipAuthorization(ctx) call so it runs
before returning any team-resolution error or not-found result in the
BatchSetAppleDDMAssets flow, while keeping the existing team_id/team_name
validation and later Authorize call unchanged.
- Around line 164-206: The DDM asset validation path in validateAppleDDMAsset
currently ignores the timestamp returned by ExpandEmbeddedSecretsAndUpdatedAt,
so secret rotations do not affect the stored asset token. Update the MDM Apple
declaration asset model and token generation flow to persist and use
secrets_updated_at on mdm_apple_declaration_assets, mirroring the behavior used
by MDMAppleDeclaration, and ensure the DDM asset token includes that value so
changes to referenced $FLEET_SECRET_* entries trigger re-delivery.

In `@server/datastore/mysql/apple_mdm.go`:
- Around line 8145-8170: The query in GetAppleDDMAssetForDelivery is missing the
same operation_type scoping used by sibling declaration-delivery paths, so it
can return assets from remove declarations. Update the SQL joined through
host_mdm_apple_declarations/MDMAppleDDMDeclarationsResponse-style logic to
filter for the install operation only, using the appropriate operation_type
condition alongside hostUUID and identifier so the delivered asset matches the
host’s currently applicable declaration state.

---

Nitpick comments:
In `@server/datastore/mysql/apple_mdm_batched.go`:
- Around line 888-923: In the batched declaration insert logic, the args slice
capacity hint is outdated because each row now appends 11 values in the loop
that builds `valueParts` and `args`. Update the `args := make([]any, 0,
len(batch)*10)` allocation in the `host_mdm_apple_declarations` batch write path
to match the current per-row argument count so the `batch` processing in this
function avoids unnecessary reallocations.

In `@server/datastore/mysql/apple_mdm_ddm_test.go`:
- Around line 700-832: testGetAppleDDMAssetForDelivery only covers team scoping
and cross-team leakage, but not device-vs-user PayloadScope isolation for asset
delivery. Add coverage that exercises GetAppleDDMAssetForDelivery with
declarations tied to both device and user channels, verifying assets referenced
only by a user-scoped declaration are not returned on the device channel and
vice versa. Use the existing helpers around handleDeclarationAsset and align the
new assertions with the behavior already validated in
testDDMChannelScopeIsolation for declarations.

In `@server/datastore/mysql/apple_mdm.go`:
- Around line 5016-5030: The asset reference insert in
SetOrUpdateMDMAppleDeclaration uses the locally generated declUUID before
reloadStmt re-reads the persisted declaration UUID, which is a footgun for the
ON DUPLICATE KEY UPDATE path. Move the mdm_apple_declaration_asset_references
insert to after the reload step, matching the labels/variables handling, so
asset references always use the actual persisted declaration_uuid. Keep the
change scoped around SetOrUpdateMDMAppleDeclaration and the assetReferences
insert block.
- Around line 4587-4649: updateDeclarationsAssetAssociations is doing
per-declaration DELETE/INSERT round trips, causing an N+1 pattern during batch
reconciliation. Refactor the loop in updateDeclarationsAssetAssociations to
batch stale-row deletion for all declaration UUIDs at once using sqlx.In, and
similarly bulk-insert all declaration_uuid/asset_uuid pairs in one multi-row
INSERT. Keep the existing reconciliation behavior and updatedDB tracking, but
avoid issuing one statement per declaration.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1565b6fb-4583-44af-8453-b2683abf2b14

📥 Commits

Reviewing files that changed from the base of the PR and between 56a3c75 and 28ff257.

📒 Files selected for processing (39)
  • changes/38986-support-ddm-assets
  • cmd/fleetctl/fleetctl/generate_gitops.go
  • cmd/fleetctl/fleetctl/generate_gitops_test.go
  • cmd/fleetctl/fleetctl/gitops_test.go
  • ee/server/service/apple_mdm.go
  • ee/server/service/apple_mdm_test.go
  • pkg/spec/gitops.go
  • pkg/spec/gitops_test.go
  • server/authz/policy.rego
  • server/authz/policy_test.go
  • server/datastore/mysql/apple_mdm.go
  • server/datastore/mysql/apple_mdm_batched.go
  • server/datastore/mysql/apple_mdm_ddm_test.go
  • server/datastore/mysql/apple_mdm_test.go
  • server/datastore/mysql/hosts_test.go
  • server/datastore/mysql/labels_test.go
  • server/datastore/mysql/secret_variables_test.go
  • server/datastore/mysql/teams_test.go
  • server/fleet/app.go
  • server/fleet/apple_mdm.go
  • server/fleet/apple_mdm_test.go
  • server/fleet/datastore.go
  • server/fleet/request.go
  • server/fleet/service.go
  • server/mdm/apple/reconcile.go
  • server/mdm/apple/reconcile_test.go
  • server/mock/datastore_mock.go
  • server/mock/service/service_mock.go
  • server/service/apple_mdm.go
  • server/service/apple_mdm_ddm_test.go
  • server/service/apple_mdm_test.go
  • server/service/client.go
  • server/service/client_profiles.go
  • server/service/client_teams.go
  • server/service/handler.go
  • server/service/integration_core_test.go
  • server/service/integration_mdm_ddm_test.go
  • server/service/integration_mdm_profiles_test.go
  • server/service/mdm.go
🛑 Comments failed to post (3)
ee/server/service/apple_mdm.go (2)

164-206: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the relevant code paths and schema references.
rg -n "validateAppleDDMAsset|CreateAppleDDMAsset|BatchSetAppleDDMAssets|MDMAppleDDMAssetToSet|SecretsUpdatedAt|AppleDeclarationForReconcile|handleDeclarationAsset|mdm_apple_declaration_assets" ee server . -g '*.go' -g '*.sql' -g '*.md' -g '*.yaml' -g '*.yml'

# Map the main file(s) before reading targeted sections.
ast-grep outline ee/server/service/apple_mdm.go --view expanded || true

# Read the relevant sections in apple_mdm.go.
sed -n '150,260p' ee/server/service/apple_mdm.go
sed -n '880,980p' ee/server/service/apple_mdm.go

# Inspect the DDM asset/declaration model types and datastore interfaces.
rg -n "type .*DDMAsset|type .*Declaration|SecretsUpdatedAt|UpdatedAt" ee server -g '*.go'

# Search for schema/SQL around the DDM asset tables.
rg -n "mdm_apple_declaration_assets|apple_ddm|ddm_asset|declaration_asset" . -g '*.sql' -g '*.go'

Repository: fleetdm/fleet

Length of output: 1949


🌐 Web query:

fleetdm fleet validateAppleDDMAsset SecretsUpdatedAt AppleDeclarationForReconcile handleDeclarationAsset mdm_apple_declaration_assets

💡 Result:

The terms provided relate to internal implementation details within FleetDM's MDM architecture, specifically concerning Declarative Device Management (DDM) and data modeling for assets and profiles. mdm_apple_declaration_assets is a database table used within FleetDM to store Apple DDM asset declarations [1]. This table is part of the schema design for managing asset lifecycle and synchronization [2]. Its design omits a scope column because an asset's channel is derived from referencing configurations [1]. SecretsUpdatedAt is a field used in FleetDM's MDM data structures, such as MDMWindowsProfilePayload [3]. It tracks the timestamp of when secrets associated with a specific profile or payload were last updated, allowing for equality checks and synchronization verification [3]. While the specific functions validateAppleDDMAsset, AppleDeclarationForReconcile, and handleDeclarationAsset were not found in public documentation, they are associated with the backend logic for asset API handlers and DDM synchronization processes within the FleetDM codebase [2]. These components are part of the broader effort to implement DDM features, involving relationships between host declarations, asset references, and the underlying asset storage tables [2].

Citations:


🌐 Web query:

site:github.com/fleetdm/fleet "validateAppleDDMAsset" "apple_mdm.go"

💡 Result:

The function validateAppleDDMAsset is associated with development work for the Fleet device management platform, specifically concerning the handling of Apple Declarative Device Management (DDM) assets [1]. It appears in the context of implementing Asset API handlers and DDM synchronization features [1]. In the development of this functionality within the fleet repository, the function is used to validate assets during the DDM sync process [1]. This involves processes such as performing a recursive walk of JSON keys that have the AssetReference suffix against an allowlist to ensure proper asset references [1].

Citations:


🌐 Web query:

site:github.com/fleetdm/fleet "AppleDeclarationForReconcile" "SecretsUpdatedAt" "apple_mdm.go"

💡 Result:

I couldn’t find an exact GitHub hit for the full string set AppleDeclarationForReconcile + SecretsUpdatedAt + apple_mdm.go in fleetdm/fleet. The closest relevant result is a Fleet PR titled “Extract Apple MDM initialization out of runServeCmd” (#46517), which suggests the code is in the Apple MDM path but not necessarily in a file indexed by that exact query. (github.com)

If you want, I can try a narrower search for the exact symbol or file path in Fleet’s repo.

Citations:


🌐 Web query:

site:github.com/fleetdm/fleet "handleDeclarationAsset" "secrets" "MDM"

💡 Result:

I couldn’t verify a public hit for that exact handleDeclarationAsset / secrets / MDM combination in fleetdm/fleet from web-indexed results. GitHub’s code search page for that repo/query is reachable, but it requires sign-in to view code results. (github.com)

If you want, I can try a broader repo search (e.g. nearby symbols like DeclarationAsset, handleDeclaration, or MDM secrets) and summarize likely matches.

Citations:


🌐 Web query:

site:github.com/fleetdm/fleet/issues/48568 DDM asset secret rotation updatedAt asset token

💡 Result:

Issue #48568 is a Fleet backend task for Apple DDM asset sync. It says asset changes should propagate via assets_updated_at (mirroring variables_updated_at) instead of folding asset tokens into the top-level token. Editing an asset updates its uploaded_at, which changes the config’s per-host token and causes the reconciler to re-poke the host automatically. (github.com)

It was opened on July 1, 2026 and is tagged as a sub-task for the Apple device product group. (github.com)

Citations:


🌐 Web query:

site:github.com/fleetdm/fleet/issues/48566 DDM asset secretsUpdatedAt

💡 Result:

Issue #48566 is “SAAD: Migrations for DDM Asset tables”, opened on July 1, 2026. It proposes adding two new tables and one column for DDM assets, including secrets_updated_at on mdm_apple_declaration_assets, plus a generated token based on raw_json and secrets_updated_at. It also adds host_mdm_apple_declarations.assets_updated_at to propagate asset changes into config sync. (github.com)

Citations:


Track secrets_updated_at for DDM assets (ee/server/service/apple_mdm.go:164-206)

validateAppleDDMAsset drops the timestamp from ExpandEmbeddedSecretsAndUpdatedAt, so rotating a referenced $FLEET_SECRET_* won’t change the stored asset token. Persist secrets_updated_at on mdm_apple_declaration_assets and include it in token generation, like MDMAppleDeclaration, so secret changes force re-delivery.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/server/service/apple_mdm.go` around lines 164 - 206, The DDM asset
validation path in validateAppleDDMAsset currently ignores the timestamp
returned by ExpandEmbeddedSecretsAndUpdatedAt, so secret rotations do not affect
the stored asset token. Update the MDM Apple declaration asset model and token
generation flow to persist and use secrets_updated_at on
mdm_apple_declaration_assets, mirroring the behavior used by
MDMAppleDeclaration, and ensure the DDM asset token includes that value so
changes to referenced $FLEET_SECRET_* entries trigger re-delivery.

233-256: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Call SkipAuthorization before returning on team lookup failures.
Otherwise authzcheck treats these paths as a missed authorization check and replaces the team not found / lookup error with CheckMissingWithResponse.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/server/service/apple_mdm.go` around lines 233 - 256, The team lookup
failure paths in BatchSetAppleDDMAssets are returning before authz is marked as
skipped, which causes authzcheck to flag them as missed checks. Move the
svc.authz.SkipAuthorization(ctx) call so it runs before returning any
team-resolution error or not-found result in the BatchSetAppleDDMAssets flow,
while keeping the existing team_id/team_name validation and later Authorize call
unchanged.
server/datastore/mysql/apple_mdm.go (1)

8145-8170: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

GetAppleDDMAssetForDelivery doesn't filter by operation_type, unlike sibling declaration-delivery queries.

MDMAppleDDMDeclarationsResponse (line 5298) explicitly filters AND operation_type = ? (install) when serving a declaration to a host. This query joins host_mdm_apple_declarations without any operation_type filter, so an asset can still be delivered to a host even while the referencing declaration is pending/verified for removal (operation_type = 'remove'), until the pending-remove row is cleaned up by MDMAppleStoreDDMStatusReport. This is a missing filtering criterion that can return unintended data for a query meant to serve "currently applicable" content for a specific host.

🐛 Proposed fix to scope delivery to install-operation declarations
 	var asset fleet.DownloadableDDMAsset
 	err := sqlx.GetContext(ctx, ds.reader(ctx), &asset, `SELECT a.asset_uuid, a.team_id, a.identifier, a.name, a.token, a.created_at, a.uploaded_at, a.raw_json
 		FROM
 			hosts h
 			JOIN host_mdm_apple_declarations hmad ON hmad.host_uuid = h.uuid
 			JOIN mdm_apple_declaration_asset_references r ON r.declaration_uuid = hmad.declaration_uuid
 			JOIN mdm_apple_declaration_assets a ON a.asset_uuid = r.asset_uuid
 			AND a.team_id = COALESCE(h.team_id, 0)
 		WHERE
 			h.uuid = ?
-			AND a.identifier = ?`, hostUUID, identifier)
+			AND a.identifier = ?
+			AND hmad.operation_type = ?`, hostUUID, identifier, fleet.MDMOperationTypeInstall)

As per path instructions: "When reviewing SQL queries that are added or modified, ensure that appropriate filtering criteria are applied—especially when a query is intended to return data for a specific entity... Check for missing WHERE clauses or incorrect filtering that could lead to incorrect or non-deterministic results."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

// GetAppleDDMAssetForDelivery returns the asset with the given identifier that
// belongs to the given team, and is referenced by the given host's current declarations.
func (ds *Datastore) GetAppleDDMAssetForDelivery(ctx context.Context, identifier string, hostUUID string) (*fleet.DownloadableDDMAsset, error) {
	if identifier == "" {
		return nil, ctxerr.New(ctx, "asset identifier is required")
	}

	var asset fleet.DownloadableDDMAsset
	err := sqlx.GetContext(ctx, ds.reader(ctx), &asset, `SELECT a.asset_uuid, a.team_id, a.identifier, a.name, a.token, a.created_at, a.uploaded_at, a.raw_json
		FROM
			hosts h
			JOIN host_mdm_apple_declarations hmad ON hmad.host_uuid = h.uuid
			JOIN mdm_apple_declaration_asset_references r ON r.declaration_uuid = hmad.declaration_uuid
			JOIN mdm_apple_declaration_assets a ON a.asset_uuid = r.asset_uuid
			AND a.team_id = COALESCE(h.team_id, 0)
		WHERE
			h.uuid = ?
			AND a.identifier = ?
			AND hmad.operation_type = ?`, hostUUID, identifier, fleet.MDMOperationTypeInstall)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, notFound("Asset").WithName(identifier)
		}
		return nil, ctxerr.Wrap(ctx, err, "getting apple ddm asset by identifier")
	}
	return &asset, nil
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/datastore/mysql/apple_mdm.go` around lines 8145 - 8170, The query in
GetAppleDDMAssetForDelivery is missing the same operation_type scoping used by
sibling declaration-delivery paths, so it can return assets from remove
declarations. Update the SQL joined through
host_mdm_apple_declarations/MDMAppleDDMDeclarationsResponse-style logic to
filter for the install operation only, using the appropriate operation_type
condition alongside hostUUID and identifier so the delivered asset matches the
host’s currently applicable declaration state.

Source: Path instructions

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

@MagnusHJensen: Sure, I'll pause automatic reviews for this PR. Let me know when you'd like me to resume.

✅ Action performed

Reviews paused.

@codecov

codecov Bot commented Jul 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 56.92308% with 336 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.08%. Comparing base (ace8cf0) to head (28ff257).
⚠️ Report is 49 commits behind head on 48568-asset-api-ddm-sync.

Files with missing lines Patch % Lines
server/service/apple_mdm.go 33.67% 119 Missing and 11 partials ⚠️
server/datastore/mysql/apple_mdm.go 74.07% 42 Missing and 21 partials ⚠️
server/service/client.go 35.86% 55 Missing and 4 partials ⚠️
ee/server/service/apple_mdm.go 67.21% 27 Missing and 13 partials ⚠️
server/service/client_profiles.go 0.00% 18 Missing ⚠️
cmd/fleetctl/fleetctl/generate_gitops.go 67.56% 7 Missing and 5 partials ⚠️
server/service/client_teams.go 0.00% 8 Missing ⚠️
server/datastore/mysql/apple_mdm_batched.go 83.33% 2 Missing and 2 partials ⚠️
pkg/spec/gitops.go 77.77% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@                     Coverage Diff                      @@
##           48568-asset-api-ddm-sync   #49046      +/-   ##
============================================================
- Coverage                     68.08%   68.08%   -0.01%     
============================================================
  Files                          3709     3710       +1     
  Lines                        235098   236219    +1121     
  Branches                      12353    12353              
============================================================
+ Hits                         160073   160822     +749     
- Misses                        60637    60935     +298     
- Partials                      14388    14462      +74     
Flag Coverage Δ
backend 69.68% <56.92%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@MagnusHJensen MagnusHJensen force-pushed the 48568-asset-api-ddm-sync branch from 2ec5c9a to 05657d0 Compare July 9, 2026 16:41
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
cmd/fleetctl/fleetctl/generate_gitops.go (1)

1579-1616: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Sanitize asset.Name before using it in a file path.

Unlike generateProfiles, which derives filenames via a dedicated generateProfileFilename helper, this uses the raw asset Name directly (assets/%s.json). If Name contains path separators or other unsafe characters, this can produce unexpected nested paths or malformed output filenames.

♻️ Suggested approach
-		fileName := fmt.Sprintf("assets/%s.json", asset.Name)
+		safeName := sanitizeFilename(asset.Name) // reuse/extract similar logic to generateProfileFilename
+		fileName := fmt.Sprintf("assets/%s.json", safeName)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/fleetctl/fleetctl/generate_gitops.go` around lines 1579 - 1616, The
generateAssets method is building filenames directly from asset.Name, which can
create unsafe or malformed paths. Update generateAssets to sanitize asset.Name
before composing the assets/%s.json filename, using a dedicated helper similar
to generateProfileFilename if needed, and keep the rest of the path construction
in generateAssets unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@cmd/fleetctl/fleetctl/generate_gitops.go`:
- Around line 1579-1616: The generateAssets method is building filenames
directly from asset.Name, which can create unsafe or malformed paths. Update
generateAssets to sanitize asset.Name before composing the assets/%s.json
filename, using a dedicated helper similar to generateProfileFilename if needed,
and keep the rest of the path construction in generateAssets unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 33c6d494-d0e5-4702-9508-10ffb6d59cc2

📥 Commits

Reviewing files that changed from the base of the PR and between 2ec5c9a and 28ff257.

📒 Files selected for processing (20)
  • cmd/fleetctl/fleetctl/generate_gitops.go
  • cmd/fleetctl/fleetctl/generate_gitops_test.go
  • ee/server/service/apple_mdm.go
  • ee/server/service/apple_mdm_test.go
  • pkg/spec/gitops.go
  • pkg/spec/gitops_test.go
  • server/datastore/mysql/apple_mdm.go
  • server/datastore/mysql/apple_mdm_ddm_test.go
  • server/fleet/app.go
  • server/fleet/apple_mdm.go
  • server/fleet/datastore.go
  • server/fleet/service.go
  • server/mock/datastore_mock.go
  • server/mock/service/service_mock.go
  • server/service/apple_mdm.go
  • server/service/client.go
  • server/service/client_profiles.go
  • server/service/client_teams.go
  • server/service/handler.go
  • server/service/mdm.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/service/integration_mdm_profiles_test.go (1)

4991-5000: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

dt is passed as a message arg, not part of the assertion.

require.Contains(t, errMsg, "is a forbidden declaration type", dt) passes dt as msgAndArgs, not as content to verify. The check now only verifies the generic substring "is a forbidden declaration type" regardless of which dt was posted, so the loop no longer confirms the error message actually names the specific forbidden type under test.

Proposed fix
-		require.Contains(t, errMsg, "is a forbidden declaration type", dt)
+		require.Contains(t, errMsg, fmt.Sprintf("%s is a forbidden declaration type", dt))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/service/integration_mdm_profiles_test.go` around lines 4991 - 5000,
The loop in integration_mdm_profiles_test.go is not asserting that the returned
error mentions the specific forbidden declaration type because dt is being
passed as msgAndArgs to require.Contains instead of being checked in the text.
Update the assertion around extractServerErrorText(res.Body) so it verifies
errMsg contains the actual dt value along with the forbidden-type message, using
the batch MDM profile test case that posts declarationForTestWithType and
iterates over fleet.ForbiddenDeclTypes.
🧹 Nitpick comments (2)
cmd/fleetctl/fleetctl/generate_gitops.go (1)

1579-1615: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Sanitize asset.Name before using it in file paths.

generateAssets uses asset.Name directly in fmt.Sprintf("assets/%s.json", asset.Name), while generateProfiles routes filenames through generateProfileFilename for sanitization. An asset name containing /, .., or OS-invalid characters could create unexpected directory structures or path traversal in the output directory.

♻️ Proposed fix
 func (cmd *GenerateGitopsCommand) generateAssets(teamId *uint, teamName string) ([]map[string]any, error) {
 	assets, err := cmd.Client.ListDDMAssets(teamId)
 	if err != nil {
 		fmt.Fprintf(cmd.CLI.App.ErrWriter, "Error getting assets: %v\n", err)
 		return nil, err
 	}
 	if len(assets) == 0 {
 		return nil, nil
 	}

 	result := make([]map[string]any, 0, len(assets))
 	for _, asset := range assets {
 		contents, err := cmd.Client.DownloadDDMAsset(asset.AssetUUID)
 		if err != nil {
 			fmt.Fprintf(cmd.CLI.App.ErrWriter, "Error getting asset contents: %s\n", err)
 			return nil, err
 		}

-		fileName := fmt.Sprintf("assets/%s.json", asset.Name)
+		safeName := sanitizeFilename(asset.Name)
+		fileName := fmt.Sprintf("assets/%s.json", safeName)
 		if teamId == nil {
 			fileName = fmt.Sprintf("lib/%s", fileName)
 		} else {
 			fileName = fmt.Sprintf("lib/%s/%s", teamName, fileName)
 		}
 		cmd.FilesToWrite[fileName] = string(contents)

 		path := fmt.Sprintf("./%s", fileName)
 		if teamId != nil {
 			path = fmt.Sprintf("../%s", fileName)
 		}
 		result = append(result, map[string]any{"path": path})
 	}

 	return result, nil
 }

A simple sanitizer could be:

func sanitizeFilename(name string) string {
	// Replace path separators and other problematic characters.
	r := strings.NewReplacer("/", "_", "\\", "_", "..", "_", ":", "_")
	return r.Replace(name)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/fleetctl/fleetctl/generate_gitops.go` around lines 1579 - 1615,
`generateAssets` is building output paths directly from `asset.Name`, which can
create unsafe or unexpected filenames. Update
`GenerateGitopsCommand.generateAssets` to pass the asset name through the same
kind of filename sanitization used for profiles (or a dedicated helper like
`sanitizeFilename`) before constructing the `assets/...json` path, so generated
files cannot include separators, traversal segments, or invalid characters.
pkg/spec/gitops.go (1)

1210-1224: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider requiring a file reference for asset entries.

expandBaseItems is called without RequireFileReference, so an assets: entry with neither path nor paths silently passes expansion and later fails in resolveAndUpdateProfilePath with a confusing failed to read profile file <cwd>-style error instead of a clear asset entry has no "path" or "paths" field. Since DDM assets always require file content (unlike some profile use cases), setting RequireFileReference: true here would surface a much clearer error.

Proposed fix
 		macOSSettings.Assets, errs = expandBaseItems(macOSSettings.Assets, controlsDir, "asset", GlobExpandOptions{
-			AllowedExtensions: map[string]bool{".json": true},
-			LogFn:             logFn,
+			AllowedExtensions:     map[string]bool{".json": true},
+			RequireFileReference:  true,
+			LogFn:                 logFn,
 		})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/spec/gitops.go` around lines 1210 - 1224, The macOSSettings.Assets
expansion is allowing asset entries with no file reference, which later produces
an unclear error from resolveAndUpdateProfilePath. Update the expandBaseItems
call in the asset-handling block to require a file reference by setting
RequireFileReference: true in the GlobExpandOptions used for assets, so entries
without path or paths fail early with a clear asset-specific validation error.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@server/service/integration_mdm_profiles_test.go`:
- Around line 4991-5000: The loop in integration_mdm_profiles_test.go is not
asserting that the returned error mentions the specific forbidden declaration
type because dt is being passed as msgAndArgs to require.Contains instead of
being checked in the text. Update the assertion around
extractServerErrorText(res.Body) so it verifies errMsg contains the actual dt
value along with the forbidden-type message, using the batch MDM profile test
case that posts declarationForTestWithType and iterates over
fleet.ForbiddenDeclTypes.

---

Nitpick comments:
In `@cmd/fleetctl/fleetctl/generate_gitops.go`:
- Around line 1579-1615: `generateAssets` is building output paths directly from
`asset.Name`, which can create unsafe or unexpected filenames. Update
`GenerateGitopsCommand.generateAssets` to pass the asset name through the same
kind of filename sanitization used for profiles (or a dedicated helper like
`sanitizeFilename`) before constructing the `assets/...json` path, so generated
files cannot include separators, traversal segments, or invalid characters.

In `@pkg/spec/gitops.go`:
- Around line 1210-1224: The macOSSettings.Assets expansion is allowing asset
entries with no file reference, which later produces an unclear error from
resolveAndUpdateProfilePath. Update the expandBaseItems call in the
asset-handling block to require a file reference by setting
RequireFileReference: true in the GlobExpandOptions used for assets, so entries
without path or paths fail early with a clear asset-specific validation error.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 33c6d494-d0e5-4702-9508-10ffb6d59cc2

📥 Commits

Reviewing files that changed from the base of the PR and between 05657d0 and 28ff257.

📒 Files selected for processing (39)
  • changes/38986-support-ddm-assets
  • cmd/fleetctl/fleetctl/generate_gitops.go
  • cmd/fleetctl/fleetctl/generate_gitops_test.go
  • cmd/fleetctl/fleetctl/gitops_test.go
  • ee/server/service/apple_mdm.go
  • ee/server/service/apple_mdm_test.go
  • pkg/spec/gitops.go
  • pkg/spec/gitops_test.go
  • server/authz/policy.rego
  • server/authz/policy_test.go
  • server/datastore/mysql/apple_mdm.go
  • server/datastore/mysql/apple_mdm_batched.go
  • server/datastore/mysql/apple_mdm_ddm_test.go
  • server/datastore/mysql/apple_mdm_test.go
  • server/datastore/mysql/hosts_test.go
  • server/datastore/mysql/labels_test.go
  • server/datastore/mysql/secret_variables_test.go
  • server/datastore/mysql/teams_test.go
  • server/fleet/app.go
  • server/fleet/apple_mdm.go
  • server/fleet/apple_mdm_test.go
  • server/fleet/datastore.go
  • server/fleet/request.go
  • server/fleet/service.go
  • server/mdm/apple/reconcile.go
  • server/mdm/apple/reconcile_test.go
  • server/mock/datastore_mock.go
  • server/mock/service/service_mock.go
  • server/service/apple_mdm.go
  • server/service/apple_mdm_ddm_test.go
  • server/service/apple_mdm_test.go
  • server/service/client.go
  • server/service/client_profiles.go
  • server/service/client_teams.go
  • server/service/handler.go
  • server/service/integration_core_test.go
  • server/service/integration_mdm_ddm_test.go
  • server/service/integration_mdm_profiles_test.go
  • server/service/mdm.go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SAAD: Gitops assets + PayloadScope handling

2 participants