Repository navigation
fix(platform): stop macOS Gatekeeper from blocking the nodeup binary - #133
Merged
Merged
Conversation
macOS marks downloaded files with the `com.apple.quarantine` extended
attribute, and refuses to execute an unsigned binary that still carries
it. The kernel kills the process before `main()` ever runs:
ASP: Security policy would not allow process: 82275, /opt/homebrew/Cellar/nodeup/1.2.1/bin/nodeup
`nodeup` ships as an unsigned Go binary (no Apple Developer ID
certificate), so this was fatal for it — the command produced no output
and no error, it just died. Homebrew quarantines every artifact it
downloads and propagates the attribute to the files it extracts, so
`brew install dipto0321/tap/nodeup` produced a `nodeup` that could not
be executed.
The repair has to happen out of band, at install time: a quarantined
executable is killed before its first instruction, so no amount of
self-repair code inside the binary can strip the attribute. Both
download channels now strip it as part of the install:
- The Homebrew cask carries a `postflight_steps` hook that runs
`xattr -dr com.apple.quarantine` on the staged path, so the
attribute is gone before the binary is linked into the prefix. This
is the remedy Homebrew's own cask guidance recommends.
- The npm wrapper's `install.js` strips the attribute from the
extracted binary before it is moved into place.
Both paths are best-effort — an install never fails because the
attribute could not be removed — and both are no-ops on platforms
without the attribute.
Verified end-to-end against the exact cask GoReleaser renders,
installed with a real `brew install --cask`: the binary comes out with
no quarantine attribute and runs 3/3 times. The control (a formula
install, which is what the tap shipped before) reproduces the original
symptom exactly: quarantine present, 0/3 runs.
Closes #132.
|
✔️ 8b109f1 - Conventional commits check succeeded. |
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.
Summary
macOS could install a
nodeupthat could not be executed at all.The command produced no output and no error — it just died.
macOS marks files downloaded from the internet with the
com.apple.quarantineextended attribute, and Gatekeeper refuses toexecute an unsigned binary that still carries it.
nodeupships asan unsigned Go binary (no Apple Developer ID certificate), so this was
fatal for it. The kernel killed the process before
main()ever ran:Homebrew quarantines every artifact it downloads and propagates the
attribute onto the files it extracts, so
brew install dipto0321/tap/nodeupproduced anodeupthat could not be run.The repair has to happen out of band, at install time — a
quarantined executable is killed before its first instruction, so no
amount of self-repair code inside the binary can strip the attribute.
Both download channels now do it as part of the install:
Homebrew cask — a
postflight_stepshook runsxattr -dr com.apple.quarantineon the staged path, so theattribute is gone before the binary is linked into the prefix. This
is the remedy Homebrew's own cask guidance recommends (and what
GoReleaser's cask docs point at).
npm wrapper —
nodeup-npm/scripts/install.jsstrips theattribute from the extracted binary before it is moved into place.
Both paths are best-effort: an install never fails because the
attribute could not be removed, and both are no-ops on platforms that
do not have the attribute.
Verification
End-to-end against the exact cask GoReleaser renders, installed
with a real
brew install --caskfrom a scratch tap:com.apple.quarantinepostflight_stepshookbin.install— what the tap shipped)The control formula reproduces the reported symptom exactly.
Ad-hoc codesigning was tested and rejected as a remedy:
codesign --force --sign -leavesspctl --assessatrejectedand the binarystill fails 0/3.
Also confirmed that the Go-level self-heal is impossible: a quarantined
binary times out and never creates the marker file that
main()writes — the kernel kills it before the first instruction.
Changes
.goreleaser.yaml—homebrew_casks[].hooks.post.install_stepsstrips the quarantine attribute on macOS.
nodeup-npm/scripts/install.js— newstripQuarantine()helper,called after the binary is moved into place.
nodeup-npm/scripts/install_test.js— 4 new tests covering theplatform gate, the probe-then-strip sequence, the recursive form, and
the "a failed strip must not abort the install" guarantee.
docs/installation.md— a new macOS Gatekeeper section: whatthe failure looks like, how each channel handles it, and the manual
recovery one-liner (
xattr -dr com.apple.quarantine) forbrowser-downloaded tarballs (tar/zip extraction propagates the
attribute too).
CHANGELOG.md— the v1.2.2 Fixed entry.Test plan
go build ./...go vet ./...gofmt -l .(clean)golangci-lint run ./...→ 0 issuesgo test -race ./...→ all packages oknode nodeup-npm/scripts/install_test.js→ 25 tests, 0 issuesgoreleaser check→ configuration validgoreleaser release --snapshot→ renders the cask with thepostflight_stepshook; the rendered file is valid Rubybrew install --caskof the rendered cask → binary has noquarantine attribute, runs 3/3
🤖 Generated with puku-cli
Closes #132