Skip to content

Developer ID sign and notarize the standalone macOS CLI binaries - #1012

Open
philmillman wants to merge 1 commit into
mainfrom
sign-macos-cli-binaries
Open

Developer ID sign and notarize the standalone macOS CLI binaries#1012
philmillman wants to merge 1 commit into
mainfrom
sign-macos-cli-binaries

Conversation

@philmillman

Copy link
Copy Markdown
Member

What

The varlock binary shipped in varlock-macos-{x64,arm64}.tar.gz was not signed at all (codesign -dvvv on an installed one reports "code object is not signed"). Only VarlockEnclave.app was signed and notarized. This signs the CLI binary too, with Developer ID, hardened runtime enabled, and no entitlement exceptions granted.

Why

Hardened runtime is the thing that stops another process running as the same user from attaching to varlock and reading resolved secret values out of its memory. Without it, the process that holds every secret varlock just fetched is the least protected artifact we ship.

Measured before/after with lldb -p against a live varlock run:

Signature lldb -p result
ad-hoc, no hardened runtime (what we ship today) Process stopped — attached
hardened runtime error: attach failed (Not allowed to attach to process)

Entitlements

Bun's codesigning guide suggests granting allow-jit, allow-unsigned-executable-memory, disable-executable-page-protection, allow-dyld-environment-variables, and disable-library-validation. None are needed. I verified the compiled binary runs under --options runtime with an empty entitlement set across --version, --help, load, run -- node, explain, scan, cache status, and keychain list — the last one being the important case, since it spawns VarlockEnclave.app and talks to it over its unix socket. Release builds with --bytecode work too.

varlock-cli.entitlements therefore lists all seven exceptions as <false/> rather than omitting them, matching the convention in VarlockEnclave.entitlements, so a future edit that flips one shows up in a diff. Worth knowing: the plist cannot carry XML comments, codesign feeds it to AMFIUnserializeXML which rejects them.

CI shape

codesign and notarytool only exist on macOS, but the other five targets cross-compile fine on linux and there was no reason to move them onto a macOS runner. So:

  • New reusable workflow build-cli-binaries-macos.yaml builds, signs, notarizes, and verifies the two macOS archives on macos-latest. It reuses the existing Apple credentials from the same 1Password item the native-binary workflows use.
  • release-binaries in release.yaml and binary-release.yaml now build with --targets= for the non-macOS five, download the macOS archives, and append their checksums.
  • build-binaries.ts gained --targets=, --sign / --no-sign, and a shasum -a 256 fallback so it can generate checksums when running on macOS.

Verification in the macOS job asserts the hardened runtime flag is set, the authority is Developer ID, and no entitlement is <true/>. It then extracts each archive and checks the signature survived tar, that no AppleDouble sidecars leaked in, and that the bundled .app kept its own signature and stapled ticket. The arm64 slice is smoke tested from the extracted archive.

Notes and tradeoffs

  • No stapling. xcrun stapler only handles bundles, disk images, and installer packages, not bare Mach-O executables. Apple publishes the ticket and Gatekeeper resolves it online, which is the normal arrangement for a signed CLI in a tarball.
  • binary-release.yaml now needs a macOS runner and Apple credentials. Its header comment previously stated it needed neither. The signed helpers are still pulled from npm; it is the CLI binary itself that has to be rebuilt and re-signed, and that cannot be recovered from the published package. Comment updated.
  • Local dev builds get an ad-hoc signature with hardened runtime, so local behavior matches release and entitlement regressions surface before a release. --no-sign opts out, which you need if you want to attach a debugger to the compiled binary.
  • Preview binaries (binary-preview.yaml) stay unsigned. They build all seven targets on linux, where signMacBinary no-ops with a log line. Worth doing separately if PR preview archives should be Gatekeeper-clean.
  • Only --options runtime is applied to the varlock Mach-O. No --deep, deliberately: re-signing the bundled VarlockEnclave.app would invalidate its stapled ticket.

Related

While measuring this I found that official Node.js ships get-task-allow, so node processes are attachable by any same-user process despite having the hardened runtime flag set. That caps what any node-based install can promise and is called out in the docs added here.

The `varlock` binary shipped in varlock-macos-{x64,arm64}.tar.gz was not
signed at all. Only VarlockEnclave.app was. That left the process that
actually holds resolved secrets with no hardened runtime, so any process
running as the same user could attach a debugger and read them out of its
memory.

Sign it with Developer ID, hardened runtime on, and no entitlement
exceptions granted. Bun's codesigning guide suggests granting five of
them; none are needed. Verified locally that the compiled binary runs
under `--options runtime` with an empty entitlement set, including the
path that spawns VarlockEnclave.app and talks to it over its unix
socket, and that `lldb -p` is refused as a result.

codesign and notarytool only exist on macOS, so the two macOS archives
now build on a macOS runner via a new reusable workflow while the other
five keep cross-compiling on linux. build-binaries.ts gained `--targets=`
for that split, `--sign` / `--no-sign`, and a shasum fallback so it can
generate checksums on macOS.

There is no stapling step: `xcrun stapler` only handles bundles, disk
images and installer packages, not bare Mach-O executables. Apple
publishes the ticket and Gatekeeper resolves it online.
@pullfrog

pullfrog Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

This run croaked 😵

The workflow encountered an error before any progress could be reported. Please check the link below for details.

Pullfrog  | Rerun failed job ➔View workflow run | via Pullfrog𝕏

@github-actions

Copy link
Copy Markdown
Contributor

bumpy-frog

The changes in this PR will be included in the next version bump.

patch Patch releases

  • varlock 1.16.1 → 1.16.2

Bump files in this PR

Click here if you want to add another bump file to this PR


This comment is maintained by bumpy.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
varlock-website 28b98a0 Commit Preview URL

Branch Preview URL
Aug 17 2026, 07:04 PM

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant