Skip to content

Add opt-in unsigned macOS release path when Apple secrets are absent - #1200

Merged
dgershman merged 2 commits into
mainfrom
feature/crow-1199-unsigned-release-path
Sep 4, 2026
Merged

Add opt-in unsigned macOS release path when Apple secrets are absent#1200
dgershman merged 2 commits into
mainfrom
feature/crow-1199-unsigned-release-path

Conversation

@dgershman

Copy link
Copy Markdown
Collaborator

Closes #1199

Summary

  • Add --skip-signing (and CROW_ALLOW_UNSIGNED=1) to scripts/macos-sign-notarize.sh so the usual CLI tarball/zip and Crow.app zip are produced without codesign, notarytool, or staple. Default remains fail-closed when secrets are missing.
  • Gate the GitHub Release path on repo/org variable CROW_ALLOW_UNSIGNED_RELEASE=true. When it is set and Apple secrets are absent, the workflow passes --skip-signing, marks the release a prerelease, and the notes say the build is unsigned and Gatekeeper-quarantined. Secrets present → signing is unchanged.
  • Document the exception in ADR 0021 so the opt-in does not silently contradict fail-closed.

Test plan

  • bash scripts/macos-sign-notarize_test.sh (55 passed, including --skip-signing and CROW_ALLOW_UNSIGNED=1)
  • shellcheck scripts/macos-sign-notarize.sh scripts/macos-sign-notarize_test.sh
  • With CROW_ALLOW_UNSIGNED_RELEASE=true and no Apple secrets, a tag (e.g. v0.2.0-rc.N) completes green and publishes a prerelease with the unsigned universal crow+crowd tarball and Crow.app, labeled unsigned
  • With the variable unset, a build lacking secrets still hard-fails at signing
  • With real secrets present, signing/notarization is unaffected

Made with Cursor

Closes #1199

Co-authored-by: Cursor <cursoragent@cursor.com>
Crow-Session: CE798EB8-CEED-482E-92C1-92F2EDCAE07D
Co-Authored-By: Claude <noreply@anthropic.com>
@dgershman dgershman added the crow:merge Crow auto-merge on green label Sep 4, 2026
@dgershman
dgershman enabled auto-merge (squash) September 4, 2026 14:51

@dhilgaertner dhilgaertner left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code & Security Review

Critical Issues (if any)

None.

Architecture / Existing Patterns

  • Existing pathway: scripts/macos-sign-notarize.sh + .github/workflows/release.yml signing step (ADR 0021). This PR extends that path with --skip-signing rather than inventing a parallel packaging flow — the right shape.
  • Green: Admin-gated CROW_ALLOW_UNSIGNED_RELEASE repo/org variable, explicit --skip-signing in CI (not CROW_ALLOW_UNSIGNED env), fail-closed default when the variable is unset, and signing unchanged when secrets are present. ADR 0021 and docs/macos-release-signing.md are updated consistently. Rejected alternatives (per-tag workflow_dispatch input) are documented.

Security Review

Strengths:

  • Unsigned publish requires an admin-controlled repository/org variable and absent signing secrets; an arbitrary tag push cannot reach the unsigned path alone.
  • Release notes and script output clearly label artifacts as unsigned and Gatekeeper-quarantined; unsigned builds are always marked prerelease.
  • --skip-signing skips codesign, notarytool, and staple entirely; no ephemeral keychain or cert import on the unsigned path.
  • SHA256 sidecars are still emitted for unsigned artifacts.

Concerns:

  • Green: Accepting CROW_ALLOW_UNSIGNED=1 locally is intentional and documented; CI does not pass that env — only the gated --skip-signing flag.

Code Quality

  • Yellow.github/workflows/release.yml: The new "Write release notes" heredocs preserve YAML indentation inside the body (confirmed: each line is emitted with ~10 leading spaces). GitHub markdown ATX headings require 0–3 leading spaces, so ## Unsigned prerelease and ## Install will not render as headings — the unsigned warning may appear as indented/code-block text instead of a prominent banner. Dedent heredoc content to column 0 (or use a checked-in template + sed).
  • Yellow.github/workflows/release.yml: The unsigned-branch gate concatenates cert/password aliases (${A:-}${B:-}${C:-}), while require_secrets() in scripts/macos-sign-notarize.sh uses first_set (first non-empty wins). If multiple alias secrets are populated during migration, the workflow may think secrets are present and attempt signing with a concatenated blob that require_secrets would never use. Reuse the same presence semantics (e.g. a small shared helper or first_set copied into the workflow step).
  • Green: macos_sign_notarize_main now resets flag state per invocation — good for testability.
  • Green: Added Crow.app existence check before packing avoids silently skipping the app zip.
  • Green: usage() awk rewrite is more robust than the fixed sed line range.
  • Green: Tests pass (bash scripts/macos-sign-notarize_test.sh — 55 passed); shellcheck clean on changed scripts.

Summary Table

Color Meaning Verdict effect
Red Must fix Request changes
Yellow Should fix Request changes
Green Consider Approve allowed

Recommendation: Request Changes — driven by [0 Red, 2 Yellow, 5 Green] findings.


🐦‍⬛ Reviewed by Crow via Cursor

Move release notes out of YAML-indented heredocs so ATX headings render,
and match signing-secret presence to first_set (first non-empty alias).

Co-authored-by: Cursor <cursoragent@cursor.com>
Crow-Session: CE798EB8-CEED-482E-92C1-92F2EDCAE07D
Co-Authored-By: Claude <noreply@anthropic.com>
@dgershman

Copy link
Copy Markdown
Collaborator Author

Addressed both yellow findings in the latest push:

  1. Release notes indent — notes now come from scripts/write-release-notes.sh with ATX headings at column 0 (unit-tested). YAML heredocs no longer wrap the markdown.
  2. Secret presence — the unsigned-path gate uses the same first_set first-non-empty alias semantics as require_secrets(), instead of concatenating env vars.

@dhilgaertner dhilgaertner left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code & Security Review

Critical Issues (if any)

None.

Architecture / Existing Patterns

  • Existing pathway: scripts/macos-sign-notarize.sh + .github/workflows/release.yml signing step (ADR 0021). This PR extends that path with --skip-signing / CROW_ALLOW_UNSIGNED and an admin-gated workflow branch — it does not invent a parallel packaging flow.
  • Green: Prior Yellow findings are resolved: release notes now come from scripts/write-release-notes.sh (ATX headings at column 0, covered by write-release-notes_test.sh), and the unsigned-path gate in .github/workflows/release.yml reuses the same first_set first-non-empty alias semantics as require_secrets() in scripts/macos-sign-notarize.sh.
  • Green: CROW_ALLOW_UNSIGNED_RELEASE is repo/org-admin gated; CI passes --skip-signing explicitly rather than CROW_ALLOW_UNSIGNED env; default remains fail-closed; signing is unchanged when secrets are present. ADR 0021, docs/macos-release-signing.md, and CHANGELOG.md are aligned.

Security Review

Strengths:

  • Unsigned publish requires admin-controlled CROW_ALLOW_UNSIGNED_RELEASE=true and absent Developer ID cert/password; an arbitrary tag push cannot reach the unsigned path alone.
  • Unsigned artifacts are always prerelease, labeled prominently in scripts/write-release-notes.sh, and skip codesign/notarytool/staple entirely (no ephemeral keychain on that path).
  • SHA256 sidecars are still emitted; CROW_VERSION continues to be validated against the VERSION file before packaging.

Concerns:

  • Green: Local CROW_ALLOW_UNSIGNED=1 / --skip-signing is intentional for developer dry-runs and is documented in docs/macos-release-signing.md; it does not weaken the GitHub Release gate.

Code Quality

  • Green: macos_sign_notarize_main resets per-invocation state; unsigned path adds a Crow.app existence check before packing; usage() awk rewrite is more robust.
  • Green: Tests pass locally: bash scripts/macos-sign-notarize_test.sh (55), bash scripts/write-release-notes_test.sh (17); shellcheck clean on changed scripts; swift build succeeds.

Summary Table

Color Meaning Verdict effect
Red Must fix Request changes
Yellow Should fix Request changes
Green Consider Approve allowed

Recommendation: Approve — driven by [0 Red, 0 Yellow, 8 Green] findings.


🐦‍⬛ Reviewed by Crow via Cursor

@dgershman
dgershman merged commit 4a95b30 into main Sep 4, 2026
5 checks passed
@dgershman
dgershman deleted the feature/crow-1199-unsigned-release-path branch September 4, 2026 15:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

crow:merge Crow auto-merge on green

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add an opt-in unsigned release path so tag builds can publish without Apple secrets

2 participants