diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5aee0f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ + +# Claude Code worktrees. These are other sessions' checkouts living inside this +# repo; `git add -A` will otherwise commit them as embedded git repositories. +.claude/worktrees/ diff --git a/infra/.claude/skills/key-gaps/SKILL.md b/infra/.claude/skills/key-gaps/SKILL.md index 3896d72..f243540 100644 --- a/infra/.claude/skills/key-gaps/SKILL.md +++ b/infra/.claude/skills/key-gaps/SKILL.md @@ -34,8 +34,18 @@ Gather the key names over SSH (never the values), then audit. Hosts/keys per INF K=~/.ssh/thermograph_agent_ed25519 # sudo: /etc/thermograph.env is root-owned (0640). On prod `agent` can read it # directly too, but sudo works uniformly on both boxes. -ssh -i $K agent@169.58.46.181 'sudo grep -oE "^[A-Z_]+=" /etc/thermograph.env' > /tmp/prod.keys # prod -ssh -i $K agent@75.119.132.91 'sudo grep -oE "^[A-Z_]+=" /etc/thermograph.env' > /tmp/beta.keys # beta +# +# The character class must allow DIGITS. This was `^[A-Z_]+=` until 2026-07-24, +# which silently skipped every key whose name contains a digit — in this estate +# that is all six *_S3_* keys. Prod audited as 26 keys against 32 real ones, and +# the audit reported the S3 credentials as missing from both hosts. They were +# present the whole time, and the phantom was independently "confirmed" twice by +# people re-running the same pattern. An audit that under-reports is worse than +# no audit: the invented finding sends someone to provision a credential that +# already exists, and it was briefly recorded as a root cause of a real bug. +# The match still stops at the `=`, so no value is ever read. +ssh -i $K agent@169.58.46.181 'sudo grep -oE "^[A-Z][A-Z0-9_]*=" /etc/thermograph.env' > /tmp/prod.keys # prod +ssh -i $K agent@75.119.132.91 'sudo grep -oE "^[A-Z][A-Z0-9_]*=" /etc/thermograph.env' > /tmp/beta.keys # beta python3 .claude/skills/key-gaps/key_gaps.py prod=/tmp/prod.keys beta=/tmp/beta.keys ``` diff --git a/infra/.claude/skills/key-gaps/key_gaps.py b/infra/.claude/skills/key-gaps/key_gaps.py index 775f7ab..a32df73 100755 --- a/infra/.claude/skills/key-gaps/key_gaps.py +++ b/infra/.claude/skills/key-gaps/key_gaps.py @@ -17,7 +17,7 @@ It reads only KEY NAMES, never values — safe to run anywhere. Sources per envi * a SOPS-encrypted YAML (deploy/secrets/.yaml) — keys are plaintext even when encrypted, so no age key or decryption is needed; or * a plain key-list file (one KEY per line, or KEY=... lines) — e.g. the output of - `ssh 'grep -oE "^[A-Z_]+=" /etc/thermograph.env'` for a live audit. + `ssh 'grep -oE "^[A-Z][A-Z0-9_]*=" /etc/thermograph.env'` for a live audit. Usage: key_gaps.py prod=deploy/secrets/prod.yaml beta=deploy/secrets/beta.yaml