How the macOS Release Signing Workflow Preserves Permission Grants in no-mistakes

The macOS release signing workflow preserves permission grants by enforcing a fixed bundle identifier (com.kunchenguid.no-mistakes) and Team ID (9T2J7MNUP9) across all releases, ensuring macOS Gatekeeper recognizes updated binaries as the same trusted application rather than requiring users to re-grant file-system, network, or accessibility permissions.

The kunchenguid/no-mistakes repository automates macOS distribution through .github/workflows/release.yml, which compiles cmd/no-mistakes/main.go and embeds version metadata from internal/buildinfo/buildinfo.go before applying cryptographic signatures. As documented in AGENTS.md, the workflow ensures that user-granted permissions survive application updates by anchoring Gatekeeper trust to a fixed identity rather than binary content. This article examines the technical invariants that prevent permission invalidation during the release process.

Fixed Identifier and Team ID as Permission Anchors

macOS stores user-granted permissions (such as file-system access or accessibility rights) against an application's unique identifier and Team ID, not against a hash of the binary itself. The workflow hard-codes these values to ensure continuity across releases.

In .github/workflows/release.yml (lines 30-35 and 85-90), the signing step explicitly sets the identifier:

codesign --force --timestamp --options runtime \
  --identifier com.kunchenguid.no-mistakes \
  --keychain "$KEYCHAIN_PATH" \
  --sign "$IDENTITY_HASH" "dist/no-mistakes"

The Team ID 9T2J7MNUP9 is extracted from the Developer ID Application certificate and validated before signing. By maintaining these constants across releases, the workflow ensures that macOS associates the new binary with existing permission grants stored in the user's security database.

Identity-Based Designated Requirements

To prevent accidental invalidation of permission grants, the workflow explicitly rejects content-based designated requirements. A cdhash requirement would tie the signature to a specific binary hash, breaking the link between versions whenever the code changes.

The verification step (lines 75-84 and 176-184) inspects the designated requirement using codesign -d -r- to confirm it uses an identity-based anchor (anchor apple generic) referencing the Team ID and identifier. This ensures the signature remains valid across different builds of the same application identity, preserving the permission grant chain.

Hardened Runtime and Secure Timestamping

The workflow applies Apple's hardened runtime and RFC-3161 timestamping to extend signature validity beyond certificate expiration. These flags are applied during the signing process (lines 29-33):

  • --options runtime: Enables the hardened runtime, which is required for notarization and ensures the app runs within security constraints compatible with modern macOS versions.
  • --timestamp: Embeds a secure timestamp proving the signature existed when the certificate was valid.

A timestamp ensures that even if the Developer ID certificate expires years later, Gatekeeper can still verify that the signature was valid at the time of signing. Without this, users would lose their permission grants when the certificate expires, as the system would treat the app as unsigned.

Strict Verification Pipeline Before Publication

Before any binary reaches the release page, the workflow executes a comprehensive verification job (lines 51-59, 64-66, 69-73, and 86-91). This job validates:

  • Signature validity: codesign --verify --strict confirms the binary is intact and unmodified.
  • Identity consistency: The output of codesign -dvvv must contain Authority=Developer ID Application, TeamIdentifier=9T2J7MNUP9, and Identifier=com.kunchenguid.no-mistakes.
  • Hardened runtime flag: The signature flags must include runtime.
  • Secure timestamp: The Timestamp= field must be present and not equal to "none".
  • Architecture integrity: The binary architecture matches the CI matrix leg, preventing silent substitution.

Any verification failure aborts the release, ensuring that only binaries meeting the signing contract are distributed to users.

Ephemeral Signing Environment

The workflow protects the signing identity by using an ephemeral keychain that exists only for the duration of the signing job (lines 74-82 and 133-138). The Developer ID certificate, provided via the CSC_LINK and CSC_KEY_PASSWORD environment variables, is imported into a temporary keychain and deleted immediately after use.

This approach prevents the private key from persisting on the GitHub Actions runner, eliminating the risk of unauthorized re-signing that could break the identifier-grant relationship or allow distribution of malicious binaries bearing the same identity.

Build and Archive Integrity

To ensure checksums reflect the final trusted artifact, the workflow signs the binary before creating the release archive (lines 94-102). The dist/no-mistakes binary is signed in place, then tar-balled and uploaded. Consequently, the SHA-256 checksum published on the release page corresponds to the signed binary, allowing downstream consumers to verify the exact artifact that passed the security pipeline.

Summary

  • Fixed identity: The workflow hard-codes com.kunchenguid.no-mistakes and Team ID 9T2J7MNUP9 to ensure Gatekeeper recognizes updates as the same application.
  • Identity-based requirements: Verification rejects cdhash anchors in favor of Team ID-based designated requirements, preserving permission grants across builds.
  • Temporal safety: RFC-3161 timestamping ensures signatures remain valid after certificate expiration.
  • Ephemeral security: The signing key exists only in memory during the CI job, preventing unauthorized re-signing.
  • Verification gates: Strict pre-publication checks ensure only compliant binaries reach users.

Frequently Asked Questions

What happens if the bundle identifier changes between releases?

If the identifier com.kunchenguid.no-mistakes changes, macOS treats the application as a completely different entity. Users must re-grant all permissions (file-system access, accessibility, network) because Gatekeeper stores grants against the identifier-Team ID pair. The workflow prevents this by hard-coding the identifier in .github/workflows/release.yml.

Why is the hardened runtime required for permission preservation?

The hardened runtime (--options runtime) is required for notarization on modern macOS versions. While it primarily enforces security policies like library validation and JIT restrictions, it also signals to Gatekeeper that the application follows modern security practices. The workflow enforces this flag (lines 64-66) to ensure compatibility with macOS security expectations and prevent runtime blocking that could trigger permission re-requests.

How does timestamping prevent future permission loss?

Without a secure timestamp (--timestamp), the signature becomes invalid when the Developer ID certificate expires. If the signature is invalid, Gatekeeper cannot verify the application's identity, forcing users to re-grant permissions. The RFC-3161 timestamp proves the signature was valid at the time of signing, allowing Gatekeeper to trust the signature indefinitely.

What occurs if the verification step fails during the release?

If any verification check fails (incorrect Team ID, missing hardened runtime flag, absent timestamp, or signature mismatch), the workflow aborts immediately and does not publish the release artifact. This prevents corrupted or improperly signed binaries from reaching users, ensuring that only binaries capable of preserving permission grants are distributed.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →