docs: audit of the 29 empty-payload .age files under secrets/ (dead / escrow-trap / degraded) #38

Merged
founder merged 1 commit from audit/empty-age-payloads into main 2026-08-16 13:08:56 +00:00
Owner

Substitute for a Forgejo issue (issues are disabled on this repo by infra/forgejo/repository.tf). The full report is the file in this PR; the same text follows so it is readable here.

Nothing was minted, restored, modified, or deleted — this is a classification only. The (a) deletions and (c) re-provisioning are decisions for Conrad.


Empty .age payload audit — 2026-08-16

29 files under secrets/ are valid age files with zero-byte plaintext (header + recipient stanzas + MAC, no payload). agenix and age -d "succeed" on them and materialise 0-byte files, so consumers degrade silently instead of failing.

How they got that way. Three commits:

Commit Date Effect
4f53e0f2 (gpt-5.2-codex) "rekey release secrets for forge" 2026-01-19 rewrote 25 files with empty payloads (a rekey run without the decrypting key: age -d failed, empty stdin was piped into age -e). Most were later restored; 8 of today's empties trace to it: finances/{credit-cards,gnosis-safe,usdc-settlement}, forge/minio-gateway, gcp/service-account, identity/agent-ssh, tuyo/bearer-token (+ dkim/staging-2025 arrived empty in the parent 6976b553).
ebc1c6c0 (Founder) "Update nix cache, identities, and governance terms" 2026-02-01 same failure shape: emptied finances/mercury-token, notes/business-metadata.nix, all four records/*.pdf, and created six identity/forwardemail placeholders empty.
65bc708d (gpt-5.3-codex) "rekey encrypted payloads to current recipients" 2026-03-16 faithful rekey — preserved every empty payload with the new recipient set.

The remaining empties were born empty (placeholders that were never filled): grafana/planetscale-prometheus-, identity/{forge,founder,runner,release-runner}-ssh, identity/-openpgp, nexmail/encryption-key, planetscale/*-database-url, roles/jmap-bearer, stalwart/dkim/staging-2025, forwardemail/imap-password.

Detection (all stanzas -> ssh-ed25519; size == 102 + 110·recipients ⇒ empty):

find secrets -name '*.age' | while read -r f; do n=$(grep -a -c '^-> ssh-ed25519 ' "$f"); s=$(wc -c < "$f"); [ $((s - 102 - 110*n)) -le 0 ] && echo "EMPTY $f"; done

Live evidence gathered on the forge (2026-08-16): /run/agenix/{gcpServiceAccount,minioGateway,rolesJmapBearer,grafanaPlanetscalePrometheusUrl,grafanaPlanetscalePrometheusToken} are all 0 bytes; the accounting-key-store container holds 0-byte /app/records/private/*.pdf and /app/notes/private/business-metadata.nix; key-store-garage-replicate.service fails every hour on the missing GCS credential; Stalwart's config (stalwart.toml and its Postgres config store) contains no signature.*/DKIM settings and no staging-2025 string anywhere — outbound mail is DKIM-signed by Forward Email under selector fe-99839b68ec (per the FE API's smtp_dns_records).

Recoverability. Every file that once had a real payload still has its last-good blob in git, encrypted to founderLegacy (Conrad's ~/.ssh/id_ed25519); I decrypted each to wc -c only (nothing written, nothing restored) — see the byte counts below. Nothing has been minted, restored, modified, or deleted by this audit.


(a) Dead — no live consumer → delete the file + secrets.nix entry (+ listed code refs)

# File Born empty / lost Consumers found Notes / cleanup
1 grafana/planetscale-prometheus-url.age born empty (1a01fb8f) age.secrets.grafanaPlanetscalePrometheusUrl (nixos/keystore-forge.nix:3966) — but hosts/keystore-forge/common.nix:2290 sets planetscale.prometheusUrlFile = null; Grafana's "PlanetScale (forge)" datasource is the local scraper 127.0.0.1:9090 fed by planetscale/{id,token} delete file + secrets.nix line + the two age.secrets blocks (keystore-forge.nix:3966-3977)
2 grafana/planetscale-prometheus-token.age born empty same same
3 planetscale/forgejo-database-url.age born empty (38d1c6fd) only the mint helpers Scripts/update-planetscale-secrets.sh, Scripts/update-forgejo-planetscale-secret.sh, Makefile forgejo-database-url, secrets/README.md §PlanetScale. Forgejo runs on local Postgres over the socket (config/databases/forgejo-socket.env); the Fly DSN path (Scripts/update-planetscale-secret.sh, singular) writes straight to flyctl secrets and never touches these files delete file + entry; retire the two helper scripts + Makefile targets + README §PlanetScale step 4 wording
4 planetscale/stalwart-database-url.age born empty as above (update-stalwart-planetscale-secret.sh, update-stalwart-database-secret.sh, Makefile stalwart-database-url); Stalwart is on local Postgres (stalwart-db-env.service) same
5 planetscale/plane-database-url.age born empty not in secrets.nix at all; Plane retired in 88474a8d (2026-03-09) delete file
6 roles/jmap-bearer.age born empty (dd21d351, renamed from codex/) age.secrets.rolesJmapBearer (forge keystore-forge.nix:3804, staging) → exported as CODEX_JMAP_BEARER_TOKEN_FILE (common.nix:1565, staging :122). No reader in key.store or the pinned dispatch repo (agent-dispatcher only forwards the env var). JMAP auth is OAuth / per-identity mailbox passwords (KEYSTORE_MCP_LOCAL_AGENT_TOKEN_FILES) since 9a99190a delete file + entry + age.secrets block + the two env lines; drop the stale codex/jmap-bearer from Makefile SECRETS
7 nexmail/encryption-key.age born empty (f68e8037) nixos/modules/services/nexmail exists but is not imported/enabled on any host; KSE-0053/0054 are Draft; services/mail/nexmail.md delete file + entry (re-mint with make secret name=nexmail/encryption-key only if NexMail ever ships)
8 stalwart/dkim/staging-2025.age born empty (6976b553) none. Nothing in Stalwart references DKIM (no signature.* in stalwart.toml, none in its DB config store, no key on disk); all outbound is relayed to smtp.forwardemail.net, which signs with fe-99839b68ec._domainkey. The published staging-2025._domainkey.key.store TXT is an orphan (public half only) and Scripts/update-mail-dns.sh will keep re-publishing it from services/mail/dkim/staging-2025.txt delete file + entry + services/mail/dkim/staging-2025.txt; then remove the orphan DNS TXT (harmless but misleading). The premise "staging-2025 signs mail today" did not hold up.
9 forwardemail/imap-password.age born empty (ebc1c6c0) docs only (docs/pgp-wkd.md §10, secrets/README.md §Forward Email, Makefile list). The imapsync mirrors use per-identity passwords (secrets/identity/<slug>-password.age), not this delete file + entry + doc lines — or fill it (make secret name=forwardemail/imap-password) if a governance-only IMAP password should be escrowed
10 finances/mercury-token.age lost in ebc1c6c0 (90 B, a real secret-token:… Mercury API token) none — only Makefile SECRETS + secrets.nix; no other "mercury" reference in the repo delete (recoverable from 262b28ad with founderLegacy if the finance program wants it back; consider rotating it at Mercury since it sat in history)
11 finances/credit-cards.age "lost" in 4f53e0f2 — but the last payload (76 B) was literally TODO: finance program docs (finances/README.md, skills/finance/README.md step 2, KSE-0004) never held real data. Delete, or fill for real via make secret name=finances/credit-cards if the finance skill is live
12 finances/gnosis-safe.age as above (65 B TODO:) finances/README.md; Scripts/finances/provision-safe.sh reads gnosis-safe.template.json, not this same
13 finances/usdc-settlement.age as above (50 B TODO:) finances/README.md same
14 identity/founder-openpgp.age, identity/forge-openpgp.age, identity/agent-openpgp.age born empty (ebc1c6c0) writer Scripts/export-openpgp-secret.sh; docs/wkd-workbook.md (every checkbox still unchecked); no reader — WKD publishes public keys only delete, or fill when the OpenPGP hierarchy is actually built (Scripts/export-openpgp-secret.sh --scope <tier> <identity>)
15 forge/minio-gateway.age lost in 4f53e0f2 (180 B, real MINIO_ROOT_USER/PASSWORD env) not in secrets.nix (a plain agenix -r never touches it); age.secrets.minioGateway in keystore-forge.nix:3888 materialises an unused 0-byte file on prod; the only reader is services.key.store.minioGateway.credentialsFile on the staging host def (hosts/keystore-forge-staging/default.nix:104-114). Prod blob storage moved to Garage (e69a8d6c); no minio unit on prod delete file + prod age.secrets block. If staging is still meant to run the MinIO→GCS gateway (KSE-0007/0009 both Draft) it needs a fresh env file (make secret name=forge/minio-gateway after adding it to secrets.nix) — but that host's gcpServiceAccountFile is also empty, so it cannot work today regardless

(b) Consumed-by-convention but working anyway — real value lives elsewhere; the empty file is a latent trap

These are escrows of the very SSH keys that are the agenix recipients / git signing identities (config/git-identities/*.pub). Nothing decrypts them at runtime; secrets/README.md §Git Signing Identities and Scripts/git-identity.sh generate describe them as the backup. Concrete symptom already observed: this workstation's ~/.ssh/key.store/{agent,founder} are 0-byte files dated 2026-07-17 — someone materialised identities from the empty escrows and got nothing.

# File Identity Where the real key lives today Trap on re-pave Fix (Conrad)
16 identity/forge-ssh.age forge@key.store = agenix recipient forge = trusted commit signer = runner age identity /root/.ssh/forge-ed25519 on the forge (also /var/lib/forgejo-runner-agent/age_keystore, /var/lib/forgejo-nsc/age_keystore, HQ, and the Forgejo repo secret AGE_FORGE_SSH_KEY) Lose the forge + HQ and the repo cannot restore the identity that decrypts every forgeAutomation/release secret → full rekey from the founder key. Recipients (founder founderLegacy forge runner) make this a founder-readable escrow, so it is worth filling make secret-file name=identity/forge-ssh file=<copy of forge-ed25519> from a host that has it
17 identity/founder-ssh.age new founder key (…IA4grzc0) Conrad's 1Password (docs/ssh-devshell.md) recovery-only; harmless until it isn't make secret-file name=identity/founder-ssh file=…
18 identity/agent-ssh.age agent@key.store (recipient on modelAgents/financeDisclosure secrets) nowhere we can find: not on the forge (/root/.ssh, dispatcher/codex/runner dirs are all the forge key), not on this workstation (0-byte). Recovered blob 6976b553:secrets/identity/agent-ssh.age (411 B) decrypts and its embedded pubkey matches the current agent recipient today the agent recipient is decorative — everything scoped modelAgents/financeDisclosure without forgeAutomation (finances/*, tuyo, agent-openpgp) is readable only by the founder keys, never by an agent restore: git show 6976b553:secrets/identity/agent-ssh.age | age -d -i ~/.ssh/id_ed25519 > /tmp/agent && make secret-file name=identity/agent-ssh file=/tmp/agent — or rotate via Scripts/git-identity.sh generate agent (then update secrets.nix, allowed_signers, rekey)
19 identity/runner-ssh.age, identity/release-runner-ssh.age runner, releaseRunner recipients (release ++ [runner] on cloud-/azure-signing etc.) no host or Forgejo secret holds these private keys; every CI decrypt uses the forge key (ageIdentityPath = /root/.ssh/forge-ed25519 for both forgejo-runner and forgejo-nsc) the recipients are decorative; not blocking anything either mint them (Scripts/git-identity.sh generate runner / release-runner) so the escrow is real, or drop the two recipients from secrets.nix and delete the escrows

(c) Consumed and actually broken / silently degraded — needs re-provisioning by Conrad

# File Lost in Consumer & observed effect What to mint
20 gcp/service-account.age 4f53e0f2 (2,347 B; SA release@key-d0t-store.iam.gserviceaccount.com, key id b629a27f…) key-store-garage-replicate (hourly) → fails every run: "GCS archival mirror never ran (missing /run/agenix/gcpServiceAccount)". PR #36 (fix/garage-replicate-hetzner-only) downgrades this to a warning (garageGcsArchiveEnabled = false). Also staging minioGateway.gcpServiceAccountFile Founder direction (2026-07-25, KSE-0182): workload identity federation, not a new key file. Interim: make secret-file name=gcp/service-account file=sa.json with roles/storage.objectAdmin on key-store-garage-archive. Do not restore the historical key blindly — check whether key b629a27f… still exists/was revoked in IAM first
21 records/2026-01-11_Formations.pdf.age (22.5 KB), records/2026-01-12_Formation_Approved.pdf.age (531 KB), records/2026-01-14_CP_575_Letter.pdf.age (12.5 KB, contains EIN), records/2026-01-14_Operating_Agreement.pdf.age (89.9 KB) ebc1c6c0 accounting-key-store container (accounting.key.store) decrypts secrets/records/*.age at boot with /root/.ssh/forge-ed255190-byte PDFs in /app/records/private; finances/records.beancount document directives point at them (Fava shows empty documents); Scripts/decrypt-records.sh same Restore from 5770331d (blobs decrypt with founderLegacy, all valid %PDF-): git show 5770331d:secrets/records/<f>.age | age -d -i ~/.ssh/id_ed25519 > <f>; make secret-file name=records/<f> file=<f> (scope financeRecords)
22 notes/business-metadata.nix.age ebc1c6c0 (887 B: legalName, EIN, banking, addresses…) same container path → 0-byte /app/notes/private/business-metadata.nix; notes/public/README.md, KSE-0023 restore from a63b62b3 the same way, or re-fill from notes/templates/business-metadata.nix
23 tuyo/bearer-token.age 4f53e0f2 (323 B JWT) finances/Makefile tuyo-snapshot / finances/scripts/tuyo_snapshot.py (TUYO_BEARER). Last snapshot data 2025-12-24; human-run only the historical JWT is almost certainly expired — re-mint from the Tuyo app and make secret name=tuyo/bearer-token (or move to (a) if the Tuyo feed is abandoned)

Follow-ups (not done here)

  • Add the detection one-liner as a CI check (and make Scripts/agenix-load-file.sh / any rekey path refuse zero-length plaintext) so a lossy rekey can never land again.
  • Two orphans are outside secrets.nix (forge/minio-gateway.age, planetscale/plane-database-url.age); Makefile SECRETS still lists the pre-rename codex/jmap-bearer.
  • Update keystore-mail-architecture notes: DKIM is Forward Email's fe-99839b68ec, not staging-2025.
  • Forgejo issues are disabled on key-store/key.store by infra/forgejo/repository.tf (has_issues = false), so this landed as a PR instead of an issue.
Substitute for a Forgejo issue (issues are disabled on this repo by `infra/forgejo/repository.tf`). The full report is the file in this PR; the same text follows so it is readable here. **Nothing was minted, restored, modified, or deleted** — this is a classification only. The (a) deletions and (c) re-provisioning are decisions for Conrad. --- # Empty `.age` payload audit — 2026-08-16 29 files under `secrets/` are valid age files with **zero-byte plaintext** (header + recipient stanzas + MAC, no payload). agenix and `age -d` "succeed" on them and materialise 0-byte files, so consumers degrade silently instead of failing. **How they got that way.** Three commits: | Commit | Date | Effect | | --- | --- | --- | | `4f53e0f2` (gpt-5.2-codex) "rekey release secrets for forge" | 2026-01-19 | rewrote 25 files with empty payloads (a rekey run without the decrypting key: `age -d` failed, empty stdin was piped into `age -e`). Most were later restored; **8 of today's empties trace to it**: finances/{credit-cards,gnosis-safe,usdc-settlement}, forge/minio-gateway, gcp/service-account, identity/agent-ssh, tuyo/bearer-token (+ dkim/staging-2025 arrived empty in the parent `6976b553`). | | `ebc1c6c0` (Founder) "Update nix cache, identities, and governance terms" | 2026-02-01 | same failure shape: emptied finances/mercury-token, notes/business-metadata.nix, all four records/*.pdf, and *created* six identity/forwardemail placeholders empty. | | `65bc708d` (gpt-5.3-codex) "rekey encrypted payloads to current recipients" | 2026-03-16 | faithful rekey — preserved every empty payload with the new recipient set. | The remaining empties were **born empty** (placeholders that were never filled): grafana/planetscale-prometheus-*, identity/{forge,founder,runner,release-runner}-ssh, identity/*-openpgp, nexmail/encryption-key, planetscale/*-database-url, roles/jmap-bearer, stalwart/dkim/staging-2025, forwardemail/imap-password. **Detection** (all stanzas `-> ssh-ed25519`; size == 102 + 110·recipients ⇒ empty): ```bash find secrets -name '*.age' | while read -r f; do n=$(grep -a -c '^-> ssh-ed25519 ' "$f"); s=$(wc -c < "$f"); [ $((s - 102 - 110*n)) -le 0 ] && echo "EMPTY $f"; done ``` **Live evidence gathered on the forge (2026-08-16):** `/run/agenix/{gcpServiceAccount,minioGateway,rolesJmapBearer,grafanaPlanetscalePrometheusUrl,grafanaPlanetscalePrometheusToken}` are all 0 bytes; the `accounting-key-store` container holds 0-byte `/app/records/private/*.pdf` and `/app/notes/private/business-metadata.nix`; `key-store-garage-replicate.service` fails every hour on the missing GCS credential; Stalwart's config (`stalwart.toml` and its Postgres config store) contains **no** `signature.*`/DKIM settings and no `staging-2025` string anywhere — outbound mail is DKIM-signed by Forward Email under selector `fe-99839b68ec` (per the FE API's `smtp_dns_records`). **Recoverability.** Every file that once had a real payload still has its last-good blob in git, encrypted to `founderLegacy` (Conrad's `~/.ssh/id_ed25519`); I decrypted each to `wc -c` only (nothing written, nothing restored) — see the byte counts below. Nothing has been minted, restored, modified, or deleted by this audit. --- ## (a) Dead — no live consumer → delete the file + `secrets.nix` entry (+ listed code refs) | # | File | Born empty / lost | Consumers found | Notes / cleanup | | --- | --- | --- | --- | --- | | 1 | `grafana/planetscale-prometheus-url.age` | born empty (`1a01fb8f`) | `age.secrets.grafanaPlanetscalePrometheusUrl` (`nixos/keystore-forge.nix:3966`) — but `hosts/keystore-forge/common.nix:2290` sets `planetscale.prometheusUrlFile = null`; Grafana's "PlanetScale (forge)" datasource is the local scraper `127.0.0.1:9090` fed by `planetscale/{id,token}` | delete file + secrets.nix line + the two `age.secrets` blocks (`keystore-forge.nix:3966-3977`) | | 2 | `grafana/planetscale-prometheus-token.age` | born empty | same | same | | 3 | `planetscale/forgejo-database-url.age` | born empty (`38d1c6fd`) | only the mint helpers `Scripts/update-planetscale-secrets.sh`, `Scripts/update-forgejo-planetscale-secret.sh`, Makefile `forgejo-database-url`, `secrets/README.md §PlanetScale`. Forgejo runs on local Postgres over the socket (`config/databases/forgejo-socket.env`); the Fly DSN path (`Scripts/update-planetscale-secret.sh`, singular) writes straight to `flyctl secrets` and never touches these files | delete file + entry; retire the two helper scripts + Makefile targets + README §PlanetScale step 4 wording | | 4 | `planetscale/stalwart-database-url.age` | born empty | as above (`update-stalwart-planetscale-secret.sh`, `update-stalwart-database-secret.sh`, Makefile `stalwart-database-url`); Stalwart is on local Postgres (`stalwart-db-env.service`) | same | | 5 | `planetscale/plane-database-url.age` | born empty | **not in `secrets.nix` at all**; Plane retired in `88474a8d` (2026-03-09) | delete file | | 6 | `roles/jmap-bearer.age` | born empty (`dd21d351`, renamed from `codex/`) | `age.secrets.rolesJmapBearer` (forge `keystore-forge.nix:3804`, staging) → exported as `CODEX_JMAP_BEARER_TOKEN_FILE` (`common.nix:1565`, staging `:122`). No reader in key.store or the pinned `dispatch` repo (agent-dispatcher only forwards the env var). JMAP auth is OAuth / per-identity mailbox passwords (`KEYSTORE_MCP_LOCAL_AGENT_TOKEN_FILES`) since `9a99190a` | delete file + entry + `age.secrets` block + the two env lines; drop the stale `codex/jmap-bearer` from `Makefile` `SECRETS` | | 7 | `nexmail/encryption-key.age` | born empty (`f68e8037`) | `nixos/modules/services/nexmail` exists but is not imported/enabled on any host; KSE-0053/0054 are Draft; `services/mail/nexmail.md` | delete file + entry (re-mint with `make secret name=nexmail/encryption-key` only if NexMail ever ships) | | 8 | `stalwart/dkim/staging-2025.age` | born empty (`6976b553`) | none. Nothing in Stalwart references DKIM (no `signature.*` in `stalwart.toml`, none in its DB config store, no key on disk); all outbound is relayed to `smtp.forwardemail.net`, which signs with **`fe-99839b68ec._domainkey`**. The published `staging-2025._domainkey.key.store` TXT is an orphan (public half only) and `Scripts/update-mail-dns.sh` will keep re-publishing it from `services/mail/dkim/staging-2025.txt` | delete file + entry + `services/mail/dkim/staging-2025.txt`; then remove the orphan DNS TXT (harmless but misleading). *The premise "staging-2025 signs mail today" did not hold up.* | | 9 | `forwardemail/imap-password.age` | born empty (`ebc1c6c0`) | docs only (`docs/pgp-wkd.md §10`, `secrets/README.md §Forward Email`, Makefile list). The imapsync mirrors use per-identity passwords (`secrets/identity/<slug>-password.age`), not this | delete file + entry + doc lines — or fill it (`make secret name=forwardemail/imap-password`) if a governance-only IMAP password should be escrowed | | 10 | `finances/mercury-token.age` | **lost** in `ebc1c6c0` (90 B, a real `secret-token:…` Mercury API token) | none — only Makefile `SECRETS` + secrets.nix; no other "mercury" reference in the repo | delete (recoverable from `262b28ad` with founderLegacy if the finance program wants it back; consider rotating it at Mercury since it sat in history) | | 11 | `finances/credit-cards.age` | "lost" in `4f53e0f2` — but the last payload (76 B) was literally `TODO:` | finance program docs (`finances/README.md`, `skills/finance/README.md` step 2, KSE-0004) | never held real data. Delete, or fill for real via `make secret name=finances/credit-cards` if the finance skill is live | | 12 | `finances/gnosis-safe.age` | as above (65 B `TODO:`) | `finances/README.md`; `Scripts/finances/provision-safe.sh` reads `gnosis-safe.template.json`, not this | same | | 13 | `finances/usdc-settlement.age` | as above (50 B `TODO:`) | `finances/README.md` | same | | 14 | `identity/founder-openpgp.age`, `identity/forge-openpgp.age`, `identity/agent-openpgp.age` | born empty (`ebc1c6c0`) | writer `Scripts/export-openpgp-secret.sh`; `docs/wkd-workbook.md` (every checkbox still unchecked); no reader — WKD publishes public keys only | delete, or fill when the OpenPGP hierarchy is actually built (`Scripts/export-openpgp-secret.sh --scope <tier> <identity>`) | | 15 | `forge/minio-gateway.age` | **lost** in `4f53e0f2` (180 B, real `MINIO_ROOT_USER/PASSWORD` env) | **not in `secrets.nix`** (a plain `agenix -r` never touches it); `age.secrets.minioGateway` in `keystore-forge.nix:3888` materialises an unused 0-byte file on prod; the only reader is `services.key.store.minioGateway.credentialsFile` on the **staging** host def (`hosts/keystore-forge-staging/default.nix:104-114`). Prod blob storage moved to Garage (`e69a8d6c`); no minio unit on prod | delete file + prod `age.secrets` block. If staging is still meant to run the MinIO→GCS gateway (KSE-0007/0009 both Draft) it needs a fresh env file (`make secret name=forge/minio-gateway` after adding it to `secrets.nix`) — but that host's `gcpServiceAccountFile` is also empty, so it cannot work today regardless | ## (b) Consumed-by-convention but working anyway — real value lives elsewhere; the empty file is a latent trap These are **escrows** of the very SSH keys that are the agenix recipients / git signing identities (`config/git-identities/*.pub`). Nothing decrypts them at runtime; `secrets/README.md §Git Signing Identities` and `Scripts/git-identity.sh generate` describe them as *the* backup. Concrete symptom already observed: this workstation's `~/.ssh/key.store/{agent,founder}` are **0-byte files dated 2026-07-17** — someone materialised identities from the empty escrows and got nothing. | # | File | Identity | Where the real key lives today | Trap on re-pave | Fix (Conrad) | | --- | --- | --- | --- | --- | --- | | 16 | `identity/forge-ssh.age` | `forge@key.store` = agenix recipient `forge` = trusted commit signer = runner age identity | `/root/.ssh/forge-ed25519` on the forge (also `/var/lib/forgejo-runner-agent/age_keystore`, `/var/lib/forgejo-nsc/age_keystore`, HQ, and the Forgejo repo secret `AGE_FORGE_SSH_KEY`) | Lose the forge + HQ and the repo cannot restore the identity that decrypts every `forgeAutomation`/`release` secret → full rekey from the founder key. Recipients (`founder founderLegacy forge runner`) make this a founder-readable escrow, so it is worth filling | `make secret-file name=identity/forge-ssh file=<copy of forge-ed25519>` from a host that has it | | 17 | `identity/founder-ssh.age` | new founder key (`…IA4grzc0`) | Conrad's 1Password (`docs/ssh-devshell.md`) | recovery-only; harmless until it isn't | `make secret-file name=identity/founder-ssh file=…` | | 18 | `identity/agent-ssh.age` | `agent@key.store` (recipient on `modelAgents`/`financeDisclosure` secrets) | **nowhere we can find**: not on the forge (`/root/.ssh`, dispatcher/codex/runner dirs are all the forge key), not on this workstation (0-byte). Recovered blob `6976b553:secrets/identity/agent-ssh.age` (411 B) decrypts and its embedded pubkey **matches** the current `agent` recipient | today the `agent` recipient is decorative — everything scoped `modelAgents`/`financeDisclosure` without `forgeAutomation` (finances/*, tuyo, agent-openpgp) is readable only by the founder keys, never by an agent | restore: `git show 6976b553:secrets/identity/agent-ssh.age \| age -d -i ~/.ssh/id_ed25519 > /tmp/agent && make secret-file name=identity/agent-ssh file=/tmp/agent` — or rotate via `Scripts/git-identity.sh generate agent` (then update `secrets.nix`, allowed_signers, rekey) | | 19 | `identity/runner-ssh.age`, `identity/release-runner-ssh.age` | `runner`, `releaseRunner` recipients (`release ++ [runner]` on cloud-/azure-signing etc.) | no host or Forgejo secret holds these private keys; every CI decrypt uses the forge key (`ageIdentityPath = /root/.ssh/forge-ed25519` for both `forgejo-runner` and `forgejo-nsc`) | the recipients are decorative; not blocking anything | either mint them (`Scripts/git-identity.sh generate runner` / `release-runner`) so the escrow is real, or drop the two recipients from `secrets.nix` and delete the escrows | ## (c) Consumed and actually broken / silently degraded — needs re-provisioning by Conrad | # | File | Lost in | Consumer & observed effect | What to mint | | --- | --- | --- | --- | --- | | 20 | `gcp/service-account.age` | `4f53e0f2` (2,347 B; SA `release@key-d0t-store.iam.gserviceaccount.com`, key id `b629a27f…`) | `key-store-garage-replicate` (hourly) → **fails every run**: "GCS archival mirror never ran (missing /run/agenix/gcpServiceAccount)". PR #36 (`fix/garage-replicate-hetzner-only`) downgrades this to a warning (`garageGcsArchiveEnabled = false`). Also staging `minioGateway.gcpServiceAccountFile` | Founder direction (2026-07-25, KSE-0182): workload identity federation, not a new key file. Interim: `make secret-file name=gcp/service-account file=sa.json` with `roles/storage.objectAdmin` on `key-store-garage-archive`. Do **not** restore the historical key blindly — check whether key `b629a27f…` still exists/was revoked in IAM first | | 21 | `records/2026-01-11_Formations.pdf.age` (22.5 KB), `records/2026-01-12_Formation_Approved.pdf.age` (531 KB), `records/2026-01-14_CP_575_Letter.pdf.age` (12.5 KB, contains EIN), `records/2026-01-14_Operating_Agreement.pdf.age` (89.9 KB) | `ebc1c6c0` | `accounting-key-store` container (accounting.key.store) decrypts `secrets/records/*.age` at boot with `/root/.ssh/forge-ed25519` → **0-byte PDFs** in `/app/records/private`; `finances/records.beancount` `document` directives point at them (Fava shows empty documents); `Scripts/decrypt-records.sh` same | Restore from `5770331d` (blobs decrypt with founderLegacy, all valid `%PDF-`): `git show 5770331d:secrets/records/<f>.age \| age -d -i ~/.ssh/id_ed25519 > <f>; make secret-file name=records/<f> file=<f>` (scope `financeRecords`) | | 22 | `notes/business-metadata.nix.age` | `ebc1c6c0` (887 B: legalName, EIN, banking, addresses…) | same container path → 0-byte `/app/notes/private/business-metadata.nix`; `notes/public/README.md`, KSE-0023 | restore from `a63b62b3` the same way, or re-fill from `notes/templates/business-metadata.nix` | | 23 | `tuyo/bearer-token.age` | `4f53e0f2` (323 B JWT) | `finances/Makefile tuyo-snapshot` / `finances/scripts/tuyo_snapshot.py` (`TUYO_BEARER`). Last snapshot data 2025-12-24; human-run only | the historical JWT is almost certainly expired — re-mint from the Tuyo app and `make secret name=tuyo/bearer-token` (or move to (a) if the Tuyo feed is abandoned) | ## Follow-ups (not done here) - Add the detection one-liner as a CI check (and make `Scripts/agenix-load-file.sh` / any rekey path refuse zero-length plaintext) so a lossy rekey can never land again. - Two orphans are outside `secrets.nix` (`forge/minio-gateway.age`, `planetscale/plane-database-url.age`); `Makefile SECRETS` still lists the pre-rename `codex/jmap-bearer`. - Update `keystore-mail-architecture` notes: DKIM is Forward Email's `fe-99839b68ec`, not `staging-2025`. - Forgejo issues are disabled on `key-store/key.store` by `infra/forgejo/repository.tf` (`has_issues = false`), so this landed as a PR instead of an issue.
docs: audit the 29 empty-payload .age files under secrets/
Some checks failed
Build: SQLite SEE / Redacted SEE compile (pull_request) Successful in 5s
Build: Apple / Check (SwiftLint) (pull_request) Successful in 17s
Build: Apple / Build (visionOS) (pull_request) Successful in 27s
Build: Apple / Build (iOS) (pull_request) Successful in 32s
Build: Apple / Build (macOS) (pull_request) Successful in 1m49s
Build: Web / Build (Web) (pull_request) Has started running
Build: KaiOS / Build (KaiOS) (pull_request) Successful in 6m26s
Build: Linux / Build (Linux x86_64) (pull_request) Failing after 56s
Build: Linux / Build (keystore-forge x86_64) (pull_request) Has been skipped
fa00406bfe
Classifies every zero-plaintext age file (from the lossy rekeys in
4f53e0f2 / ebc1c6c0) as dead, escrow-only, or silently degraded, with
consumers, live evidence from the forge, and what to mint. No secret
material was modified, minted, or restored.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
founder force-pushed audit/empty-age-payloads from fa00406bfe
Some checks failed
Build: SQLite SEE / Redacted SEE compile (pull_request) Successful in 5s
Build: Apple / Check (SwiftLint) (pull_request) Successful in 17s
Build: Apple / Build (visionOS) (pull_request) Successful in 27s
Build: Apple / Build (iOS) (pull_request) Successful in 32s
Build: Apple / Build (macOS) (pull_request) Successful in 1m49s
Build: Web / Build (Web) (pull_request) Has started running
Build: KaiOS / Build (KaiOS) (pull_request) Successful in 6m26s
Build: Linux / Build (Linux x86_64) (pull_request) Failing after 56s
Build: Linux / Build (keystore-forge x86_64) (pull_request) Has been skipped
to a360944467
All checks were successful
Release: If Needed / Check (Release Needed) (push) Successful in 5s
Deploy: Web / Deploy (Web) (push) Successful in 2m17s
Deploy: API / Deploy (API) (push) Successful in 2m57s
2026-08-16 13:08:54 +00:00
Compare
founder deleted branch audit/empty-age-payloads 2026-08-16 13:08:56 +00:00
Sign in to join this conversation.
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
key-store/key.store!38
No description provided.