macOS Release Signing with a Permanent Identity in no-mistakes: Requirements and Implementation
The no-mistakes project enforces a strict, test-driven contract requiring all macOS releases to be signed with an immutable Developer ID Application identity (Team ID 9T2J7MNUP9 and identifier com.kunchenguid.no-mistakes) that can never change after the first signed release.
The kunchenguid/no-mistakes repository implements rigorous controls for macOS release signing to ensure binary integrity and trust. According to the source code in workflow_release_signing_test.go and documentation in AGENTS.md, every darwin artifact must adhere to a permanent identity scheme supported by specific architectural, verification, and security constraints. These requirements guarantee that distributed binaries maintain a stable cryptographic identity across all updates.
Permanent Identity Requirements
The foundation of macOS release signing in no-mistakes rests on an immutable identity pair that is locked after the initial signed release.
Developer ID Application and Team ID
All macOS binaries must be signed using a Developer ID Application certificate associated with Team ID 9T2J7MNUP9. As specified in workflow_release_signing_test.go (lines 27‑29), this Team ID is hardcoded into the test suite and represents the organizational unit that must appear in the signature's subject.OU field. The repository treats this value as a permanent invariant that validates the origin of every distributed executable.
Immutable Identifier Contract
The signing identifier must be exactly com.kunchenguid.no-mistakes and must remain constant across all releases. The test TestReleaseWorkflowFailsClosedOnBadSignature explicitly validates this identifier (lines 40‑60), ensuring that any deviation from this permanent identifier causes the release workflow to fail. This contract prevents identifier drift that could break Gatekeeper validation for existing users.
Build Platform and Architecture Requirements
The project mandates specific execution environments to ensure consistent signing behavior and keychain security.
Architecture Support
The release workflow must produce signed binaries for both macOS architectures: amd64 (Intel) and arm64 (Apple Silicon). In workflow_release_signing_test.go, the variable signingDarwinArches (line 35) defines this dual-architecture requirement, and the test suite validates that both variants undergo the identical signing and verification pipeline.
macOS Runner Constraints
Every darwin build job must execute on a genuine macOS runner. The test function runsOnMacOS() (lines 22‑23) asserts that each job configuration specifies runs-on: macos-*, ensuring that codesign and security command-line tools execute within a native macOS environment where keychain access and code signing APIs function correctly.
Signing Workflow Implementation
The actual signing process follows a rigid procedure defined in .github/workflows/release.yml and enforced by the test suite.
codesign Flags and Options
Each signing step must invoke codesign with specific flags that establish runtime hardening and identity permanence. According to the signStepIndex implementation (lines 41‑44), the command must include:
--signwith the certificate reference--options runtimeto enable hardened runtime--identifier com.kunchenguid.no-mistakesto set the permanent bundle ID--timestampto provide cryptographic timestamping for signature validity
- name: Sign binary
run: |
export TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
codesign --sign "$CSC_LINK" \
--options runtime \
--identifier com.kunchenguid.no-mistakes \
--timestamp "$TIMESTAMP" \
no-mistakes-darwin-amd64
Timestamp Handling
The signing script must generate a TIMESTAMP environment variable at runtime. The test TestReleaseWorkflowFailsClosedOnBadSignature (lines 61‑64) asserts that the workflow fails if this timestamp is empty or set to the string "none", preventing regression to ad-hoc or timeless signatures that could expire or fail to validate against Apple's servers.
Ephemeral Keychain Security
Rather than using the default login keychain, the workflow must create a temporary keychain for each job. As verified in TestReleaseWorkflowScopesSigningSecretsToDarwin (line 40), the keychain password must be generated at runtime using openssl rand, ensuring that signing credentials exist only for the duration of the CI job:
export KEYCHAIN_PASSWORD=$(openssl rand -hex 12)
security create-keychain -p "$KEYCHAIN_PASSWORD" temp.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" temp.keychain
Verification and Security Controls
After signing, the workflow must verify the cryptographic identity and reject any anomalous signatures before publication.
Strict Verification Checks
The verification step, validated by verifyStepIndex and related assertions (lines 40‑60), must execute codesign --verify --strict and confirm:
- Team ID:
9T2J7MNUP9appears in the signature - Identifier:
com.kunchenguid.no-mistakesmatches exactly - Anchor:
anchor apple genericvalidates the certificate chain - Runtime flag: Hardened runtime is enabled
- Rejection criteria: Ad-hoc signatures and content-hash (
cdhash) requirements are explicitly rejected
Secrets Scoping and Environment Gates
Signing secrets must be isolated to prevent unauthorized access. The test TestReleaseWorkflowScopesSigningSecretsToDarwin (lines 28‑38) mandates that jobs accessing CSC_LINK and CSC_KEY_PASSWORD must be gated behind the release-signing environment, and no other workflow job may reference these secrets. This scoping limits exposure of sensitive certificates to the minimal necessary execution context.
Keychain Lifecycle Management
The temporary keychain must be deleted regardless of job success or failure. TestReleaseWorkflowCleansUpKeychainAlways (lines 78‑85) requires a cleanup step with if: always() that executes security delete-keychain to ensure that even if signing or verification fails, the ephemeral keychain containing the imported certificate is destroyed.
Step Ordering and Dependencies
The workflow imposes a strict temporal ordering to prevent unsigned artifacts from reaching release channels.
Mandatory Step Sequence
Within each darwin job, steps must appear in the exact order: sign → verify → archive → upload. The test TestReleaseWorkflowSignsBeforeArchiveAndChecksum (lines 71‑86) validates this sequence, ensuring that archives contain only verified signed binaries and that no upload occurs before verification completes.
Checksum Dependencies
The checksums generation job must depend on each darwin build job completing successfully. As enforced by the same test (lines 89‑103), this dependency ensures that cryptographic checksums cover the signed (not unsigned) archives, preventing hash mismatches or tampering between signing and distribution.
Phase-1 Scope Limitations
Currently, the workflow explicitly excludes advanced macOS distribution features. TestReleaseWorkflowStaysPhase1NoNotarization (lines 62‑68) verifies that the workflow contains no notarization, stapling, pkgbuild, universal binary creation, or Homebrew tooling. Only the core signing verification described above is permitted in this phase, simplifying the security model while maintaining Gatekeeper compatibility for direct binary distribution.
Summary
- Permanent identity: macOS releases must use Developer ID Application with immutable Team ID
9T2J7MNUP9and identifiercom.kunchenguid.no-mistakes, enforced byworkflow_release_signing_test.go(lines 27‑29). - Dual architecture: Both
amd64andarm64binaries require signing on native macOS runners (signingDarwinArches, line 35). - Strict codesign flags: Commands must include
--options runtime,--identifier, and--timestamp(signStepIndex, lines 41‑44). - Verification requirements:
codesign --verify --strictmust confirm Team ID, identifier, anchor, and reject ad-hoc signatures (lines 40‑60). - Security isolation: Secrets
CSC_LINKandCSC_KEY_PASSWORDare restricted to therelease-signingenvironment, and ephemeral keychains are generated withopenssl rand(lines 28‑40). - Step ordering: The sequence sign → verify → archive → upload is mandatory, with checksums depending on signed artifacts (lines 71‑103).
- Keychain cleanup: Temporary keychains must delete with
if: always()to ensure execution on failure (lines 78‑85).
Frequently Asked Questions
What happens if the Team ID or identifier changes after the first release?
The release workflow will fail closed. The test suite in workflow_release_signing_test.go explicitly validates the permanent identity values (lines 27‑29), and TestReleaseWorkflowFailsClosedOnBadSignature (lines 40‑60) rejects any signature that does not match Team ID 9T2J7MNUP9 or identifier com.kunchenguid.no-mistakes. This prevents identity drift that would invalidate existing Gatekeeper trust caches.
Why does the workflow use an ephemeral keychain instead of the default login keychain?
Ephemeral keychains minimize the attack surface and ensure credential isolation. According to TestReleaseWorkflowScopesSigningSecretsToDarwin (line 40), the keychain password must be generated at runtime using openssl rand, and TestReleaseWorkflowCleansUpKeychainAlways (lines 78‑85) mandates deletion with if: always(). This guarantees that signing certificates exist only for the duration of the job, even if the runner is compromised or accidentally persisted.
What is the purpose of the release-signing environment gating?
The release-signing environment acts as a mandatory access control boundary. As tested in TestReleaseWorkflowScopesSigningSecretsToDarwin (lines 28‑38), jobs that reference CSC_LINK or CSC_KEY_PASSWORD must declare this environment, and no other job in the workflow may access these secrets. This scoping prevents lateral movement of signing credentials and ensures that certificate access requires explicit environment approval if configured in GitHub repository settings.
Why are notarization and universal binaries excluded from Phase-1?
The current implementation enforces a minimal signing-only phase to reduce complexity and attack surface. TestReleaseWorkflowStaysPhase1NoNotarization (lines 62‑68) explicitly verifies the absence of notarization, stapling, pkgbuild, and universal binary tooling. This constraint ensures the workflow focuses exclusively on correct identity signing and verification before introducing the additional complexity of notarization workflows or binary aggregation.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →