ReviewOS

also looking at this

craft-native/craft

fix(packaging): stop macOS packages relocating themselves elsewhere

#46
Merged glennmichael123 wants to merge fix/pkg-bundle-relocation into main
4 files +164 -16
11 days ago

Every .pkg craft has ever produced can install itself somewhere other than where it says, and report success.

createPKG let pkgbuild infer its component. pkgbuild's default for a bundle is BundleIsRelocatable = true, so the package carries:

<relocate><bundle id="dev.example.app"/></relocate>

That directive tells installer the payload path is only a suggestion. It looks the bundle identifier up on the target volume and, if the system has a copy of that bundle registered anywhere, writes the payload over that copy instead — and exits 0. The user gets "The install was successful" and finds nothing at /Applications.

I had this wrong, twice

I told you this was Intel-specific. It isn't — darwin-arm64 failed with a byte-identical ENOENT in an adjacent run, on the same code and the same runner image.

And the relocation directive alone isn't the cause either — it's present in packages from green runs too. It's the enabler; the trigger is a registered copy of the same bundle id existing when the install happens. That's why it's intermittent, and what actually separates green from red is how long the job had already been running:

lifecycle step beganoutcome
~22 minutes into the joball 3 failing macOS legs
20–124 seconds inall 8 passing macOS legs

Which is what a race against the system's bundle indexer looks like. The lifecycle script leaves two copies of the fixture bundle in its work directory — those are the relocation target.

The fix

Write the component plist instead of inferring it, with BundleIsRelocatable off. Verified on a real fixture package, not just in the unit test:

relocate:          <relocate/>
install root mode: drwxr-xr-x  "."
payload exe count: 1

Empty <relocate/> — nothing for installer to redirect to — with the payload unchanged.

Note the pkg-info attribute relocatable="false" appears either way and does not govern this; only the <relocate> element does. I built both variants side by side to confirm that, because that attribute is exactly the thing that makes the current package look fine on inspection.

BundleIsVersionChecked is off too: with it on, installing an older version over a newer one is skipped — silently turning the rollback step, and a user's rollback, into a no-op.

Two things found alongside

  • The package asked for / to be 0700. The staging dir came from mkdtemp (mode 0700), and pkgbuild records the --root directory's own mode as the mode of . — which under --install-location / is the target volume's root. Measured: drwx------ ".". Now staged in a 0755 child, measured drwxr-xr-x.
  • The failure left no evidence. installer reporting success while writing elsewhere surfaced only as a bare ENOENT from the launch step, which cannot distinguish "nothing installed" from "installed elsewhere". The receipt is now recorded after each install, into report.json and the uploaded evidence — location: is where the payload actually landed. The workflow uploads the whole evidence directory too; the compiled fixtures sat outside the old upload path, which is why this needed a workflow dispatch to diagnose rather than an artifact download.

Verification

  • bun test — 471 pass, including 3 new ones; I confirmed they discriminate by flipping BundleIsRelocatable back to true and watching 18 pass / 1 fail
  • tsc --noEmit clean, pickier clean
  • real fixture package rebuilt and inspected with pkgutil --expand / lsbom / pkgutil --payload-files

This is a user-facing bug, not a CI one. The lifecycle workflow is just the thing that noticed.

4 changed files on the files tab, with 0 review threads.

Sign in to comment on this pull request.