Skip to content

pkg-attach and pkg-audit: an uploaded pkg that no group carries is inert - #74

Merged
rcurranmoz merged 2 commits into
mainfrom
pkg-attach-and-orphan-audit
Aug 19, 2026
Merged

pkg-attach and pkg-audit: an uploaded pkg that no group carries is inert#74
rcurranmoz merged 2 commits into
mainfrom
pkg-attach-and-orphan-audit

Conversation

@rcurranmoz

Copy link
Copy Markdown
Collaborator

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 (read-only)

Inverts every assignment group's app relationship, then names any pkg no group carries.

$ reprovision pkg-audit
  · 23 custom pkg(s) in the account (39 apple-store app(s) excluded), 20 attached to at least one group
  ▲ 1 bundle id(s) uploaded more than once with a copy attached to nothing:
  ▲     com.github.munki.pkg.p_role_gecko_t_osx_1400_r8
  ▲         630818  'p_role_gecko_t_osx_1400_r8-1.0-Signed'   ATTACHED TO NOTHING
  ▲         630822  'p_role_gecko_t_osx_1400_r8-1.0-wrapped'  ATTACHED TO NOTHING
  ▲ 5 custom pkg(s) attached to NOTHING — uploaded but inert

Both filters exist because the first live run buried its own findings:

  • apple-store apps excluded by default. 39 entries for 1Password, Duo, Google Drive and friends, which legitimately reach iOS devices by other means. --include-store restores the full view.
  • duplicates only when a copy is unattached. 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), both deliberate. A duplicate that includes a stray upload is the smell.

pkg-attach

Attaches, then verifies by re-reading the group rather than trusting the POST — same reason step_add_to_group re-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:

  • 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 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:

app bundle attached to
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
630818 / 630822 …p_role_gecko_t_osx_1400_r8 nothing
627567 …p_role_gecko_t_osx_1400_r8_staging nothing

So relops_infra_as_code/osx_pkg_blueprints has never been the source of truth for what the fleet runs — the blueprint-built uploads were never attached, and p_role_gecko_t_osx_1500_m4 isn't in the account at all. Also worth noting r8 prod is served by a bundle id of just PuppetRole, generic enough that a second upload under that name would collide.

Testing

  • 264 tests pass, ruff clean; 16 new tests covering the guards, verification, app resolution, and both audit filters.
  • pkg-audit exercised 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

rcurranmoz and others added 2 commits August 19, 2026 12:32
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>
@rcurranmoz
rcurranmoz requested a review from a team as a code owner August 19, 2026 16:36
@rcurranmoz
rcurranmoz merged commit e201c26 into main Aug 19, 2026
3 checks passed
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.

1 participant