Skip to content

docs(security): updater signing-key plan + tighten P013#30

Merged
shyhunter merged 1 commit into
mainfrom
docs/updater-signing-plan
Apr 29, 2026
Merged

docs(security): updater signing-key plan + tighten P013#30
shyhunter merged 1 commit into
mainfrom
docs/updater-signing-plan

Conversation

@shyhunter
Copy link
Copy Markdown
Owner

Summary

Adds .security/updater-signing-plan.md — the playbook for handling the Tauri updater private key, written ahead of any key existing on disk so the security posture is decided before the rollout begins. Tightens P013 to point at the plan and document an explicit rotation-cadence exception.

Closes the P013 follow-up flagged in the 2026-04-28 → 2026-04-29 security-hardening session handoff: "bake the plan in BEFORE flipping the updater on."

What this commit does

  • Creates .security/ (mirrors SuperCharge's convention; will also hold key-rotation-timeline.md once the first key is generated)
  • Adds the 228-line plan covering: threat model, storage matrix, key-gen procedure, three-phase rollout, bridge-release rotation, incident response with hard-fork escape hatch, and a hard-gates checklist
  • Tightens P013 in .claude/project_rules_decisions.md — adds a pointer to the plan and spells out the rotation cadence

What this commit does NOT do

  • No key generated (still dormant on every dimension)
  • No secrets added to GitHub Actions
  • No Rust-side tauri-plugin-updater dep
  • No tauri.conf.json change
  • No release.yml change

This is pure preparation. The doc enumerates the exact code/config diffs required at each rollout phase, but applying them is intentionally a separate PR.

Why annual rotation, not 90 days

R007 sets the global secret-rotation floor at 90 days. The plan deviates to annual + on-leak for the Tauri updater key only, with reasoning:

Tauri uses offline minisign verification — every installed app embeds the pubkey from the build it was made from. A rotation cannot be a single-PR action; it requires a bridge release signed with the OLD key whose embedded tauri.conf.json carries the NEW pubkey, followed by a release signed with the NEW key (verified by clients now carrying the new pubkey). Two release cycles per rotation. The marginal security gain over annual is small for a solo project where one person controls both the dev machine and the GitHub secrets; the friction cost of doing this every 90 days is high.

The exception is scoped to this key only. R007 still applies to every other secret class.

Out of scope

  • Apple Developer notarization (separate item — fixes the macOS "damaged" warning, orthogonal to update integrity)
  • Windows EV code-signing certificate (same idea — orthogonal)
  • Self-hosted update endpoint (GitHub Releases is adequate for a solo project)
  • Delta updates / partial bundles (defer)

Security notes (R015)

This is a docs-only change; it adds no code, no secret material, no secret-bearing config. Net security posture strictly improves:

  • Forcing the plan to exist before the key exists is the explicit intent of P013 ("bake the plan in BEFORE flipping the updater on")
  • The plan itself contains no secrets, infrastructure detail, or anything that would assist an attacker — it is a procedure document
  • The exception to R007 is documented and scoped, not silent

Risks introduced: none. Risks the plan commits us to mitigating when implementation begins:

  • GitHub Actions secrets become high-value targets — mitigation: tag-only triggers + branch protection on main + repo-admin-only secret access (all already in place)
  • The "active": false rollout phase is a footgun if forgotten — mitigation: explicit phase gates in §5
  • The bridge-release ceremony is itself a risk if mis-executed — mitigation: §6 spells the procedure step by step, and §5 Phase 3 mandates a rotation drill within 14 days of going live

Test plan

  • markdownlint .security/updater-signing-plan.md (if configured) — passes
  • Plan reads top-to-bottom without forward references that aren't resolved
  • .security/ is not in .gitignore (verified — it isn't)
  • Linked file paths and shell commands are syntactically correct
  • P013 still reads as a self-contained rule; the plan reference adds detail without making P013 incomprehensible without it

🤖 Generated with Claude Code

Adds .security/updater-signing-plan.md — the playbook for handling the
Tauri updater private key, written ahead of any key existing on disk so
the security posture is decided before the rollout begins.

Covers: threat model, storage matrix (private key in GitHub Actions
secrets only; public key embedded in tauri.conf.json), one-time key
generation procedure, three-phase rollout (plan → wired with active:false
→ activate), bridge-release rotation procedure, incident response with
hard-fork escape hatch, and a hard-gates checklist.

Rotation cadence is annual + on-leak — a documented exception to global
rule R007's 90-day default, justified by Tauri's offline-verification
model (every installed app embeds the pubkey, so rotation requires a
two-release bridge ceremony that does not scale to 90-day cycles for a
solo project).

P013 in .claude/project_rules_decisions.md is tightened to point at the
plan and to spell out the rotation cadence exception.

No code, no key material, no secrets installed. The Tauri auto-updater
remains dormant: `@tauri-apps/plugin-updater` is in package.json but the
Rust dep is absent, tauri.conf.json plugins block is empty, and the
release workflow does not see TAURI_SIGNING_* secrets. This commit only
prepares the playbook to follow when each of those flips on.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@shyhunter shyhunter merged commit 4095324 into main Apr 29, 2026
16 checks passed
@shyhunter shyhunter deleted the docs/updater-signing-plan branch April 29, 2026 08:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant