Metadata
| Status | done |
|---|---|
| Assigned | agent-813 |
| Created | 2026-07-25T20:56:54.997886544+00:00 |
| Started | 2026-07-26T05:54:53.330807389+00:00 |
| Completed | 2026-07-26T06:14:52.697495553+00:00 |
| Tags | release, signing, security, ci |
| Tokens | 4895705 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)
- macOS — Developer ID + notarytool + stapler (both
x86_64-apple-darwinandaarch64-apple-darwinjobs):- Import a
.p12Developer 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(andnex).xcrun notarytool submit <archive>.zip --apple-id ... --team-id ... --password ... --waitthenxcrun stapler staple. (altoolis RETIRED — Apple TN3147; usenotarytool.)- Gate
notarytool/stapleronrunner.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.
- Import a
- Windows — Authenticode (the
x86_64-pc-windows-msvcjob):- 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-staticand NASM install.
- Preferred: Azure Trusted Signing (keyless/certless, Microsoft-recommended 2026 path) via
- 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). - 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 stapleon bothwgandnexBEFORE archiving, gated onrunner.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=4on macOS shows the Developer ID;signtool verify /pa /v(or the Azure manifest) on Windows shows the Authenticode signature. -
The existing Sigstore
gh attestation verifystill 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.plistis minimal and correct for a CLI that does network + filesystem (no entitlements that would breakreqwest/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
- 2026-07-25T20:56:54.903631684+00:00 Task paused
- 2026-07-25T20:57:31.870519161+00:00 Task published
- 2026-07-26T05:54:53.397793209+00:00 Spawned by coordinator --executor pi --model zai:glm-5.2 --isolation required-worktree
- 2026-07-26T06:01:55.231149417+00:00 Starting: will add macOS Developer ID + notarytool + stapler, and Windows Authenticode (Azure Trusted Signing preferred, signtool fallback), to release.yml build-matrix jobs BEFORE archiving; keep existing Sigstore attestations; skip-with-banner when certs absent; surface unsigned status in release notes. New macos-entitlements.plist.
- 2026-07-26T06:12:32.334525290+00:00 Implementation complete. release.yml: macOS codesign+notarytool+stapler (gated runner.os==macOS, before archiving, with certs-absent skip+::warning::) on both apple targets; Windows Azure Trusted Signing (preferred) + signtool PFX fallback (both RFC3161-timestamped), gated runner.os==Windows; existing actions/attest@v4 Sigstore provenance preserved (runs after archiving). Per-target signing-status JSON flows into assemble, which surfaces an 'unsigned archives' section in Release notes. macos-entitlements.plist: minimal hardened-runtime plist (network.client + allow-unsigned-executable-memory; no App Sandbox, no library-validation weakening — keeps reqwest/keyring working). Validated: YAML parses, actionlint clean (only 2 pre-existing errors at lines 24/138), shellcheck clean on all bash steps, release-notes node script tested for both all-signed and unsigned cases, record-status JSON valid for all 5 OS paths, new static-contract smoke scenario passes.
- 2026-07-26T06:12:55.146278577+00:00 Committed: 1d6a8daf — pushed to remote (wg/agent-813/impl-binary-signing-notarization)
- 2026-07-26T06:13:28.841838100+00:00 Task pending eval (agent reported done; awaiting `.evaluate-*` to score)
- 2026-07-26T06:14:52.697497003+00:00 Consumed durable verdict verdict-evalp-bf03335c30533fb679ae9562-evaluate-e2d6a81bac638767 exactly once: score=0.91, outcome=done