feat: stable macOS code-signing in the reusable release workflow + darwin-gate - #24
Conversation
New macos-codesign-setup composite: imports the family code-signing cert (apple-actions/import-codesign-certs), stages codesign-darwin.sh onto the runner, and exports SIGN_IDENTITY / TOOL_IDENTIFIER / CODESIGN_DARWIN_SCRIPT / REQUIRE_SIGNING. Self-gated all-or-none on the cert inputs, so an opt-out caller no-ops and keeps releasing unsigned. Extend darwin-gate with a pure, unit-tested assert-dr subcommand and a macOS check-signature wrapper: parse only the `designated =>` requirement line and assert it pins the expected cert leaf + org.open-cli-collective.<binary> identifier with no cdhash. Self-gated on expected-leaf. Wire both into release.yml behind four optional workflow_call secrets, called unconditionally (secrets.* is not available in if:); signing setup and the signature check activate together exactly when a caller passes the cert secrets, keeping the rolling v1 bump from breaking untouched callers. Closes #23
macos-codesign-setup now counts cert-leaf-sha in its gate, so a caller that passes p12/password/cn but omits leaf-sha fails fast instead of signing while darwin-gate (which gates check-signature on leaf-sha) silently skips DR enforcement. Keeps signing and enforcement strictly atomic per caller.
|
Codex PR review (9a). STATUS: blockers=0 majors=1 — the major (all-or-none must span all four secrets) is fixed in the follow-up commit; re-review below. Findings:
Verification note: I could not rerun the shell tests in this read-only sandbox because temp-file creation was denied; this review is based on the diff and file inspection. |
TDD coverage assessmentAll 16 tests pass on Linux (macOS toolchain tests correctly skipped). assert-dr — the main testable unitCoverage is solid for a shell utility. All six cases that matter are present:
Gaps worth considering (not blocking):
macos-codesign-setup all-or-none gateThe bash gate logic (count secrets, fail on 1–3, no-op on 0, proceed on 4) is pure and testable but currently has no unit tests. For a CI action gate that controls whether signing is enforced or silently skipped this is the one place worth adding a small test. Suggested cases:
These could live in a Correctly NOT tested (exercised at release time)
Verdict: the core |
Move the all-or-none decision into a unit-tested gate.sh (the action calls it and fails fast on a partial config) and add test_gate.sh + a CI job. This is the invariant that keeps signing and DR enforcement atomic, so it deserves coverage. Also add two assert-dr edge cases (first designated => line wins; empty identifier is rejected).
monit-reviewer
left a comment
There was a problem hiding this comment.
Automated PR Review
Reviewed commit: f047db2
Summary
| Reviewer | Findings |
|---|---|
| harness-engineering:harness-architecture-reviewer | 3 |
| harness-engineering:harness-enforcement-reviewer | 1 |
| harness-engineering:harness-knowledge-reviewer | 1 |
harness-engineering:harness-architecture-reviewer (3 findings)
actions/macos-codesign-setup/action.yml:59
Third-party action
apple-actions/import-codesign-certs@v6is pinned to a floating major-version tag rather than a commit SHA. This action receives the raw P12 bytes and password (the private signing key). If the upstreamv6tag is moved to a malicious commit, signing key material can be exfiltrated silently. Pin to a full commit SHA to make the code handling cert material immutable.
💡 Suggestion - actions/macos-codesign-setup/action.yml:57
No guard enforcing that the job runs on a macOS runner when signing is enabled. If a caller wires all four secrets but runs on ubuntu-latest (e.g., a matrix job that forgot to filter), the workflow fails with an opaque keychain error from the upstream action. Adding
if: steps.gate.outputs.enabled == 'true' && runner.os == 'macOS'on the cert-import step (or a check inside gate.sh) would surface the misconfiguration immediately.
💡 Suggestion - actions/macos-codesign-setup/codesign-darwin.sh:19
After signing, the script only checks that the designated requirement does not pin
cdhash; it does not verify the cert leaf SHA or identifier against expected values. A misconfigured cert CN (wrong cert in the keychain) produces a structurally valid signature that passes this check but fails only at thedarwin-gatestep — after a full multi-target goreleaser run has already completed. Asserting the leaf and identifier immediately after signing would catch cert misconfiguration at the first binary.
harness-engineering:harness-enforcement-reviewer (1 findings)
💡 Suggestion - actions/darwin-gate/darwin-gate.sh:135
check_signaturesilences codesign diagnostic output with2>/dev/null. When verification fails, the custom::error::names the binary but discards the codesign reason (e.g., 'code object is not signed at all', 'a sealed resource is missing'). Redirect stderr to a variable or remove the suppression so the codesign message appears alongside the custom error without polluting the happy-path output.
harness-engineering:harness-knowledge-reviewer (1 findings)
💡 Suggestion - actions/macos-codesign-setup/gate.sh:14
The
::error::workflow commands in gate.sh are written to stderr (>&2), but GitHub Actions only processes workflow commands from stdout. The step will still exit 1 correctly, but the structured annotations will not appear in the Actions UI — they will show as plain stderr text. Remove>&2so annotations render correctly.
3 info-level observations excluded. Run with --verbose to include.
2 PR discussion threads considered.
Completed in 8m 06s | $1.12 | sonnet | daemon 0.2.121 | Glorfindel
| Field | Value |
|---|---|
| Model | sonnet |
| Reviewers | hybrid-synthesis, harness-engineering:harness-architecture-reviewer, harness-engineering:harness-enforcement-reviewer, harness-engineering:harness-knowledge-reviewer |
| Engine | claude · sonnet |
| Reviewed by | pr-review-daemon · monit-pr-reviewer |
| Duration | 8m 06s wall · 12m 54s compute (Reviewers: 6m 28s · Synthesis: 1m 34s) |
| Cost | $1.12 |
| Tokens | 197.9k in / 45.7k out |
| Turns | 8 |
Per-workstream usage
| Workstream | Model | In | Out | Cache read | Cache create | Cost |
|---|---|---|---|---|---|---|
| hybrid-synthesis | sonnet | 32.2k | 6.4k | 18.6k | 13.5k (1h) | $0.16 |
| harness-engineering:harness-architecture-reviewer | sonnet | 71.4k | 8.5k | 49.1k | 22.2k (1h) | $0.24 |
| harness-engineering:harness-enforcement-reviewer | sonnet | 49.2k | 22.7k | 6.9k | 42.3k (1h) | $0.51 |
| harness-engineering:harness-knowledge-reviewer | sonnet | 45.2k | 8.1k | 23.0k | 22.2k (1h) | $0.22 |
Re-reviews only run when @monit-reviewer is re-requested as a reviewer — push as many commits as you need, then re-request when ready. PRs targeting branches other than main, master are skipped, even when @monit-reviewer is re-requested.
- SHA-pin apple-actions/import-codesign-certs (fe74d46, v6.1.0): it receives the raw .p12 + password, so a moved @v6 tag would be silent key exfiltration. - macos-codesign-setup: fail with a clear error if signing is enabled on a non-macOS runner instead of an opaque keychain failure later. - gate.sh now signals via exit code (0 enabled / 10 disabled / 1 invalid) and emits its ::error:: on stdout so the Actions UI renders the annotation; the action branches on the code. - darwin-gate check-signature: stop suppressing codesign stderr so the failure reason ('not signed', 'sealed resource missing', ...) is visible.
monit-reviewer
left a comment
There was a problem hiding this comment.
Automated PR Review
Reviewed commit: 0f37420 | Previous: f047db2 (incremental)
Summary
No issues found.
7 PR discussion threads considered.
Completed in 47s | $0.73 | sonnet | daemon 0.2.121 | Glorfindel
| Field | Value |
|---|---|
| Model | sonnet |
| Mode | Re-review · Cycle 2 · Session resumed |
| Reviewers | hybrid-synthesis, harness-engineering:harness-architecture-reviewer, harness-engineering:harness-enforcement-reviewer, harness-engineering:harness-knowledge-reviewer |
| Engine | claude · sonnet |
| Reviewed by | pr-review-daemon · monit-pr-reviewer |
| Duration | 47s wall · 42s compute (Reviewers: 6s · Synthesis: 18s) |
| Cost | $0.73 |
| Tokens | 194.4k in / 2.2k out |
| Turns | 4 |
Per-workstream usage
| Workstream | Model | In | Out | Cache read | Cache create | Cost |
|---|---|---|---|---|---|---|
| hybrid-synthesis | sonnet | 42.4k | 839 | 18.6k | 23.8k (1h) | $0.11 |
| harness-engineering:harness-architecture-reviewer | sonnet | 37.0k | 74 | 4.0k | 33.0k (1h) | $0.13 |
| harness-engineering:harness-enforcement-reviewer | sonnet | 51.2k | 76 | 4.0k | 47.1k (1h) | $0.18 |
| harness-engineering:harness-knowledge-reviewer | sonnet | 36.6k | 73 | 4.0k | 32.6k (1h) | $0.12 |
| discussion-summarizer | — | 27.2k | 1.1k | 0 | 22.4k (1h) | $0.19 |
Re-reviews only run when @monit-reviewer is re-requested as a reviewer — push as many commits as you need, then re-request when ready. PRs targeting branches other than main, master are skipped, even when @monit-reviewer is re-requested.
|
Codex final pass (9e) — post-daemon. STATUS: blockers=0 majors=0 minors=0 nits=0 — no findings. No findings. The post-daemon changes preserve the approved architecture: the all-or-none gate now spans all four signing inputs, the runner guard is correctly scoped to the enabled path, |
Shared-infra ticket for the stable macOS code-signing initiative (convention:
cli-commondistribution.md§2A). Lets every keychain-backed CLI get a stable code-signing identity so the Keychain "Always Allow" grant survivesbrew upgrade.What's here
actions/macos-codesign-setupcomposite +codesign-darwin.sh: imports the cert (apple-actions/import-codesign-certs@v6), stages the per-build signing script to$RUNNER_TEMP(outside the repo, so it can't dirty goreleaser's git-state check), exportsSIGN_IDENTITY/TOOL_IDENTIFIER=org.open-cli-collective.<binary>/CODESIGN_DARWIN_SCRIPT/REQUIRE_SIGNING. All-or-none self-gate on the cert inputs (secrets.*isn't usable inif:).darwin-gate: new pure, unit-testedassert-dr(readscodesign -d -r-on stdin, parses only thedesignated =>line, asserts cert leaf + identifier, nocdhash, case-insensitive leaf, ignoresCDHash=metadata) + a macOScheck-signaturewrapper. Self-gated onexpected-leaf.release.yml: 4 optionalworkflow_callsecrets;macos-codesign-setupbefore the snapshot build;expected-leaf/identifierpassed to the gate. Atomic per caller — signing + enforcement both off, or both on, so the rollingv1bump can't break untouched callers.Verification
bash actions/darwin-gate/test_darwin_gate.sh— 16/16 pass (6 newassert-drcases).shellcheckclean;actionlintclean; all YAML parses.On merge
The rolling
v1tag is advanced to this commit so the@v1callers pick up the new action + gate, then the CLI tickets (#184, #154, #128, #106, #408) opt in.Closes #23