impl-binary-signing-notarization

Impl: macOS/Windows binary signing + notarization in release pipeline

Metadata

Statusdone
Assignedagent-813
Created2026-07-25T20:56:54.997886544+00:00
Started2026-07-26T05:54:53.330807389+00:00
Completed2026-07-26T06:14:52.697495553+00:00
Tagsrelease, signing, security, ci
Tokens4895705 in / 50292 out

Description

Objective

Close the two REAL gaps the npm-distribution study flags: the macOS binaries are currently UNSIGNED/un-notarized (users hit Gatekeeper) and the Windows .exe is not Authenticode-signed (users hit SmartScreen). npm distribution raises the stakes — the binary now reaches users who never opted into "I accept this is unsigned." Add OS code-signing to the existing release pipeline BEFORE archiving, while keeping the existing Sigstore actions/attest@v4 attestations as the supply-chain-provenance layer.

Context: docs/studies/wg-npm-distribution-design.md §6.2 (macOS), §6.3 (Windows), §6.4 (the stacked provenance layers), §10.2 (recommended hardening). docs/studies/roadmap-rate-limit-and-distribution.md §1/§4(T6). This is the FIRST task of the independent release track (parallel to the whole daemon track T1-T5). T7 (npm publish) depends on this for file-safety (both edit .github/workflows/release.yml).

What to build (add to .github/workflows/release.yml, in the BUILD matrix jobs, BEFORE the archive step)

  1. macOS — Developer ID + notarytool + stapler (both x86_64-apple-darwin and aarch64-apple-darwin jobs):
    • Import a .p12 Developer ID cert from a secret into a temporary keychain.
    • codesign --force --options runtime --timestamp --entitlements macos-entitlements.plist --sign "Developer ID Application: <name>" target/<triple>/release/wg (and nex).
    • xcrun notarytool submit <archive>.zip --apple-id ... --team-id ... --password ... --wait then xcrun stapler staple. (altool is RETIRED — Apple TN3147; use notarytool.)
    • Gate notarytool/stapler on runner.os == macOS.
    • Note: include_dir! embeds the pi-worksgood JS bundle at compile time; codesigning signs the final linked binary, so the embedded bytes are covered automatically — no extra step.
  2. Windows — Authenticode (the x86_64-pc-windows-msvc job):
    • Preferred: Azure Trusted Signing (keyless/certless, Microsoft-recommended 2026 path) via azure/trusted-signing-action.
    • Or an EV/OV cert from a CA signed with signtool (Windows runner) / osslsigncode, with a TIMESTAMP so the signature stays valid after cert expiry.
    • Keep the existing RUSTFLAGS=-C target-feature=+crt-static and NASM install.
  3. Keep the existing Sigstore attestations (actions/attest@v4) as the build-provenance layer — they prove which commit/workflow produced the bytes; OS signing proves WG identity (trusted CA chain). They are complementary (study §6.4 stacked layers).
  4. Fail loudly if certs are absent — if the required secrets are not configured, the signing step should SKIP with a clear "certs missing" banner (so a release is not blocked on signing) but the GitHub Release notes must mark those archives as unsigned. Flag this so an operator knows to acquire certs before claiming GA.

Secrets / account actions required (an org/account must provide)

  • macOS: APPLE_DEVID_P12 (+ password), APPLE_ID, APPLE_TEAM_ID, APPLE_APP_PASSWORD (app-specific for notarytool). Apple Developer Program membership ($99/yr).
  • Windows: Azure Trusted Signing account, OR an EV/OV code-signing cert.
  • If these are unavailable, implement the SKIP-with-banner path and document the gap; do NOT block the release.

File scope

  • .github/workflows/release.yml (the two macOS + one Windows build-matrix jobs)
  • new macos-entitlements.plist

Implement directly.

Validation

  • The macOS matrix jobs run codesign + notarytool submit --wait + stapler staple on both wg and nex BEFORE archiving, gated on runner.os == macOS.
  • The Windows matrix job signs the .exe (Azure Trusted Signing or signtool) with a timestamp.
  • The signed binaries are verifiable: codesign -dv --verbose=4 on macOS shows the Developer ID; signtool verify /pa /v (or the Azure manifest) on Windows shows the Authenticode signature.
  • The existing Sigstore gh attestation verify still passes on every archive (signing did not break provenance).
  • CI run on a release-test-* / dry-run-* tag shows the signing steps executing (or the SKIP-with-banner path if secrets are absent).
  • If certs are unavailable, the signing step SKIPs cleanly (release not blocked) AND the gap is surfaced in the Release notes.
  • macos-entitlements.plist is minimal and correct for a CLI that does network + filesystem (no entitlements that would break reqwest/keyring).
  • A dry-run release produces signed archives; spot-check one archive's signature locally.
  • No model pin; routes through the active zai profile.

Depends on

Required by

Log