pkg-attach and pkg-audit: an uploaded pkg that no group carries is inert - #74
Merged
Conversation
Uploading a pkg and attaching it to an assignment group are separate operations in
SimpleMDM, and an unattached app does nothing at all with nothing surfacing that
fact. On 2026-08-19 p_role_tart_worker was built, signed, uploaded -- and left
unattached, while the eight hosts it existed for carried no /etc/puppet_role. It
was caught only because someone queried the group's app list by hand. Nothing in
the toolchain could answer "is this pkg actually going to reach anything?".
`pkg-audit` answers it for the whole account: invert every assignment group's app
relationship, then name any app no group carries. Read-only, API-only.
`pkg-attach` does the attach and then VERIFIES by re-reading the group, rather
than trusting the POST -- the same reason step_add_to_group re-reads: with this
API a 2xx is not evidence the state changed. It resolves an app by id or by a
unique substring of its name or bundle id, and on an ambiguous match it lists the
candidates instead of picking one.
Two guards matter more than the happy path:
- Production groups are refused, reusing PROTECTED_GROUP_IDS. Attaching a NEW
app to a live prod group pushes it to every member; 2017918 has 130+ devices
taking work. This is the "never add the bootstrap pkg to a production group"
footgun in its other form, so it fails before any HTTP call.
- `--push` is OFF by default. push_apps re-pushes EVERY app in the group to
EVERY member, including whatever postinstalls they run, so a default-on push
would let an innocuous attach re-run the bootstrap pkg on hosts that are
mid-task. Without it the pkg still lands, at each device's next check-in --
which on these boxes is often boot-only, so it can take a reboot. The
affected device count is printed either way so the choice is informed.
The audit deliberately only considers assignment groups. An app delivered solely
via a legacy device group would show as an orphan; that is right for this fleet
(verified 2026-08-19: the m4 and tart devices all have device_group_id: None, so
assignment groups are the only live path) but the docstring says so.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…an orphan The first live run buried its findings. Including apple-store apps meant 39 entries for 1Password, Duo, Google Drive and friends -- which legitimately reach iOS devices by other means -- listed alongside the five real ones. And flagging every duplicate bundle id surfaced com.mozilla.pkg.SignerBootstrap (six copies, one per signer group: vpn/tb/fx/dep/adhoc/ff-ent) and com.puppetlabs.puppet-agent (ARM and Intel builds), both entirely deliberate. So: consider only app_type == custom by default, with --include-store to restore the full view; and report a duplicate bundle id only when at least one copy is attached to nothing, since a duplicate that includes a stray upload is the smell and an all-attached set is just per-flavour variants. What that leaves on this account is worth reading. Five custom pkgs are attached to nothing, and among them the r8 role pkg is uploaded TWICE -- 630818 '-Signed' and 630822 '-wrapped', both com.github.munki.pkg.p_role_gecko_t_osx_1400_r8, neither carried by any group. Chasing that shows the fleet is served by differently-named pkgs entirely: 636989 Puppet Role - 1400 Prod com.github.munki.pkg.PuppetRole -> gecko-t-osx-1400-r8 637744 Puppet Role 1500 Prod ...p_role_1500 -> 5 m4 groups 637743 Puppet Role 1500 Staging ...p_role_1500_staging -> m4-staging 690299 p_role_tart_worker ...p_role_tart_worker -> Tart The blueprint-built uploads (p_role_gecko_t_osx_1400_r8, _staging) were never attached, and p_role_gecko_t_osx_1500_m4 is not in the account at all. So the osx_pkg_blueprints repo has never been the source of truth for what the fleet runs, and r8 prod is served by a bundle id of just "PuppetRole", generic enough that a second upload of that name would collide. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uploading a pkg and attaching it to an assignment group are separate operations in SimpleMDM, and an unattached app does nothing at all — with nothing surfacing that fact.
On 2026-08-19
p_role_tart_workerwas built, signed, uploaded, and left unattached, while the eight hosts it existed for carried no/etc/puppet_role. It was caught only because someone queried the group's app list by hand. Nothing in the toolchain could answer "is this pkg actually going to reach anything?"pkg-audit(read-only)Inverts every assignment group's app relationship, then names any pkg no group carries.
Both filters exist because the first live run buried its own findings:
--include-storerestores the full view.com.mozilla.pkg.SignerBootstrap(six copies, one per signer group — vpn/tb/fx/dep/adhoc/ff-ent) andcom.puppetlabs.puppet-agent(ARM and Intel), both deliberate. A duplicate that includes a stray upload is the smell.pkg-attachAttaches, then verifies by re-reading the group rather than trusting the POST — same reason
step_add_to_groupre-reads: with this API a 2xx is not evidence the state changed. Resolves an app by id or a unique substring of name/bundle id, and on an ambiguous match lists candidates instead of picking one.Two guards matter more than the happy path:
PROTECTED_GROUP_IDS. Attaching a new app to a live prod group pushes it to every member; 2017918 has 130+ devices taking work. This is the "never add the bootstrap pkg to a production group" footgun in its other form, so it fails before any HTTP call.--pushis off by default.push_appsre-pushes every app in the group to every member, including postinstalls — so a default-on push would let an innocuous attach re-run the bootstrap pkg on hosts that are mid-task. Without it the pkg lands at each device's next check-in, which on these boxes is often boot-only. The affected device count is printed either way.What the audit found, which is the interesting part
The fleet's role pkgs are not the ones this repo's sibling blueprints build:
636989Puppet Role - 1400 Prodcom.github.munki.pkg.PuppetRole637744Puppet Role 1500 Prod…p_role_1500637743Puppet Role 1500 Staging…p_role_1500_staging690299p_role_tart_worker…p_role_tart_worker630818/630822…p_role_gecko_t_osx_1400_r8627567…p_role_gecko_t_osx_1400_r8_stagingSo
relops_infra_as_code/osx_pkg_blueprintshas never been the source of truth for what the fleet runs — the blueprint-built uploads were never attached, andp_role_gecko_t_osx_1500_m4isn't in the account at all. Also worth noting r8 prod is served by a bundle id of justPuppetRole, generic enough that a second upload under that name would collide.Testing
ruffclean; 16 new tests covering the guards, verification, app resolution, and both audit filters.pkg-auditexercised live against the real account (read-only) — output above.pkg-attach's write path (POST /assignment_groups/{id}/apps/{app_id}) is not yet exercised live; today's attach was done through the UI. The read paths it depends on (apps(),assignment_group_app_ids()) are live-verified. Worth confirming on the next real attach.🤖 Generated with Claude Code