feat(secrets): project-keys wizard for P7, and per-project age keygen #144

Merged
jfraeys merged 1 commit from jfraeys/feat/project-keys-wizard into main 2026-10-13 21:29:06 -04:00
Owner

P7's remaining work (#118) is the keys, which only the operator can hold. This is the guided path.

  • scripts/project-keys-wizard.sh [project] [repo-path] (default forge-agent): preflight; operator key -> Keychain sops-age/<project>; recovery key -> personal Bitwarden sops-age-recovery-<project>; offline copy of the recovery key (path you choose, 0600, verified, never printed); fills the two .sops.yaml placeholders; encrypts secrets/dev.sops.yml from the local .env using the names in .env.example (python -> sops encrypt --filename-override on a pipe, so values are never printed or written unencrypted); runs sops exec-env ... go run ./dev integration; runs the isolation drill with the real keys both ways (forge-agent operator/recovery cannot decrypt infra's vault; infra operator/recovery cannot decrypt dev.sops.yml); writes docs/secrets-recovery.md in the project with the public keys.
  • Re-runnable: existing Keychain/Bitwarden items are not overwritten; their public keys are derived instead.
  • age-operator-keygen.sh / age-recovery-keygen.sh take an optional project name; default infra, so nothing existing changes.

Checked: bash -n; the .env -> sops encrypt pipe tested with throwaway keys and fake values (both recipients decrypt; values with :, # and spaces survive). Not run end to end: it needs Keychain, Bitwarden and the scratch forge.

Run: scripts/project-keys-wizard.sh forge-agent

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ge5tovcwdEUBtPwUktsZZZ

P7's remaining work (#118) is the keys, which only the operator can hold. This is the guided path. - `scripts/project-keys-wizard.sh [project] [repo-path]` (default `forge-agent`): preflight; operator key -> Keychain `sops-age/<project>`; recovery key -> personal Bitwarden `sops-age-recovery-<project>`; offline copy of the recovery key (path you choose, 0600, verified, never printed); fills the two `.sops.yaml` placeholders; encrypts `secrets/dev.sops.yml` from the local `.env` using the names in `.env.example` (python -> `sops encrypt --filename-override` on a pipe, so values are never printed or written unencrypted); runs `sops exec-env ... go run ./dev integration`; runs the isolation drill with the real keys both ways (forge-agent operator/recovery cannot decrypt infra's vault; infra operator/recovery cannot decrypt `dev.sops.yml`); writes `docs/secrets-recovery.md` in the project with the public keys. - Re-runnable: existing Keychain/Bitwarden items are not overwritten; their public keys are derived instead. - `age-operator-keygen.sh` / `age-recovery-keygen.sh` take an optional project name; default `infra`, so nothing existing changes. Checked: `bash -n`; the `.env` -> `sops encrypt` pipe tested with throwaway keys and fake values (both recipients decrypt; values with `:`, `#` and spaces survive). Not run end to end: it needs Keychain, Bitwarden and the scratch forge. Run: `scripts/project-keys-wizard.sh forge-agent` 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Ge5tovcwdEUBtPwUktsZZZ
feat(secrets): project-keys wizard for P7, and per-project age keygen
All checks were successful
Lint / lint (pull_request) Successful in 1m52s
Secret scan / scan (pull_request) Successful in 14s
f8cfe4bdce
scripts/project-keys-wizard.sh walks the operator through giving a repo its own
operator key (Keychain) and recovery key (Bitwarden), an offline copy, the
.sops.yaml recipients, encrypting secrets/dev.sops.yml from the local .env
(piped straight into sops; values never printed), the integration run, and the
isolation drill with the real keys in both directions. Re-runs derive public
keys from existing items instead of overwriting them.

age-operator-keygen.sh and age-recovery-keygen.sh take an optional project name
(default infra, so existing behaviour is unchanged).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ge5tovcwdEUBtPwUktsZZZ
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jfraeys/infra!144
No description provided.