# macOS Release Signing with a Permanent Identity in no-mistakes: Requirements and Implementation

> Learn the requirements for macOS release signing with a permanent identity in no-mistakes. Enforce immutable Developer ID signing for trustworthy releases.

- Repository: [Kun Chen/no-mistakes](https://github.com/kunchenguid/no-mistakes)
- Tags: how-to-guide
- Published: 2026-07-12

---

**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`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/.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:

- `--sign` with the certificate reference
- `--options runtime` to enable hardened runtime
- `--identifier com.kunchenguid.no-mistakes` to set the permanent bundle ID
- `--timestamp` to provide cryptographic timestamping for signature validity

```yaml
- 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:

```bash
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**: `9T2J7MNUP9` appears in the signature
- **Identifier**: `com.kunchenguid.no-mistakes` matches exactly
- **Anchor**: `anchor apple generic` validates 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 `9T2J7MNUP9` and identifier `com.kunchenguid.no-mistakes`, enforced by [`workflow_release_signing_test.go`](https://github.com/kunchenguid/no-mistakes/blob/main/workflow_release_signing_test.go) (lines 27‑29).
- **Dual architecture**: Both `amd64` and `arm64` binaries 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 --strict` must confirm Team ID, identifier, anchor, and reject ad-hoc signatures (lines 40‑60).
- **Security isolation**: Secrets `CSC_LINK` and `CSC_KEY_PASSWORD` are restricted to the `release-signing` environment, and ephemeral keychains are generated with `openssl 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`](https://github.com/kunchenguid/no-mistakes/blob/main/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.