Skip to content

Latest commit

 

History

History
88 lines (71 loc) · 4.53 KB

File metadata and controls

88 lines (71 loc) · 4.53 KB

Release runbook

Blurt is built, signed, notarized, and published locally from the maintainer's Mac via scripts/release.sh (see the script headers for the two-run bump→publish flow). This file covers the security-critical custody and policy decisions that aren't obvious from the scripts.

Signing key custody

The Developer ID Application key (602F699488189767137DF15633B967B1371ACD86, team B2VQF7Q2QY) is the root of trust: Gatekeeper accepts anything signed with it, so every published DMG must carry this signature. Protect it accordingly:

  • Keep the private key in a dedicated keychain that stays locked at rest (~/Library/Keychains/blurt-signing.keychain-db), not the login keychain, with a tight partition-list ACL (only codesign / productsign may use it) and a 15-minute auto-lock. Never store a plaintext .p12 on disk or in cloud sync; keep the encrypted .p12 backup offline.

  • scripts/release-build.sh unlocks that keychain for the duration of the build and re-locks it on exit (even on failure). It resolves the keychain password from, in order: BLURT_SIGNING_KEYCHAIN_PASSWORD (a CI secret, or op read inline), then BLURT_SIGNING_KEYCHAIN_OP (an op://… 1Password reference the script runs op read on — Touch ID fires), then an interactive prompt. It does not read the password from the login keychain, so the unlock secret never sits in login. Two equivalent local options:

    # Inline: read once at invocation
    BLURT_SIGNING_KEYCHAIN_PASSWORD="$(op read 'op://Employee/Blurt signing keychain/password')" \
      scripts/release.sh
    
    # Or export the reference once (e.g. in your shell profile) so a bare
    # `scripts/release.sh` unlocks via 1Password. Set OP_ACCOUNT if you're signed
    # into more than one 1Password account.
    export OP_ACCOUNT=assemblyai.1password.com
    export BLURT_SIGNING_KEYCHAIN_OP='op://Employee/Blurt signing keychain/password'
    scripts/release.sh

    Override the keychain path with BLURT_SIGNING_KEYCHAIN if it lives elsewhere.

  • If blurt-signing.keychain-db isn't present, the script falls back to whatever identity is on the existing search list (e.g. login) — so a machine that hasn't migrated the key yet still builds.

  • It then preflights that the identity is present (security find-identity -v -p codesigning) and refuses to build without it.

  • Everyday dev builds never touch this key. The Debug / Debug-Local configs sign with the Apple Development cert (login keychain, same team B2VQF7Q2QY), so scripts/dev-build.sh and Xcode builds work with the release keychain locked. Only release-build.sh uses the Developer ID key.

Rotating the signing certificate

If the key is compromised (or the cert expires):

  1. Revoke the Developer ID Application certificate in the Apple Developer portal.
  2. Issue a new Developer ID Application certificate on the same team (B2VQF7Q2QY).
  3. Update IDENTITY in scripts/release-build.sh to the new cert's SHA-1 (security find-identity -v -p codesigning).
  4. Cut a fresh notarized release.

Rotating to a new cert within the same team (B2VQF7Q2QY) is seamless for users — Gatekeeper accepts any valid Developer ID from any team, and updates are a manual DMG download (see Updates in AGENTS.md), so there is no signing-requirement pin to break. A team change (new Team ID) is still worth avoiding on principle and announcing, but it no longer strands existing users the way the former in-app updater's team-pinned requirement did.

Rotating the notary credential

The notary Apple-ID app-specific password lives in the blurt-notary keychain profile (submit-only; it cannot sign). To rotate: revoke the old app-specific password at appleid.apple.com, mint a new one, and re-run xcrun notarytool store-credentials blurt-notary --apple-id <you> --team-id B2VQF7Q2QY --password <new-app-specific-password>.

A bad release: roll forward, never roll back

Blurt does not yank published releases. The update check only ever offers users a strictly higher version (UpdateChecker compares SemanticVersion and reports .available only when the latest tag is greater), so the fix for any bad build is to ship a new patch via scripts/release.sh.

The one exception is a fault caught before announcing, while the same version is still safe to overwrite (e.g. a corrupted upload flagged by the post-publish verification step): use scripts/release-publish.sh --republish to replace the artifacts on the same tag. A code bug is never a republish — bump a patch.