secrets: project-scoped secrets go in /secrets/<project>/

Operator convention, 2026-08-20. If a secret belongs to one project it lives in that project's
directory rather than in files/ with the project name baked into the filename:
/secrets/lichen/test-pds.env, not /secrets/files/lichen-test-pds.env. files/ is reserved for
things genuinely shared across projects.

A flat directory forces every name to carry its own scope, which nobody does consistently, and
then 'what does this project hold' and 'what do I revoke if it is compromised' both need a grep.
A directory answers both by listing. The convention already existed in practice (b1,
notplants-orchestrator, emily-sandbox) and was simply never written down.

The symlink rule is unchanged: a consumer insisting on a fixed path gets a symlink into
/secrets/<project>/, so the file still exists exactly once.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3LdmEL7CvCYTNpoBq1kce
This commit is contained in:
2026-08-20 22:08:17 +00:00
co-authored by Claude Fable 5
parent 23391cef2b
commit e1ba9b39be
+21 -4
View File
@@ -7,12 +7,29 @@ in .env files, a private key at mode 0644. Anything in a repo is one `git add -A
a push. So: ONE encrypted file, OUTSIDE every git tree, and a helper every project uses.
/secrets/store.yaml sops+age ciphertext (0600) — values our code reads
/secrets/files/ real files (0600) SYMLINKED from the fixed path a third
party insists on: ~/.ssh keys, a systemd EnvironmentFile,
nix authKeyFile, a TLS keypair
/secrets/<project>/ PROJECT-SCOPED secrets (0600): everything belonging to one
project lives together, e.g. /secrets/lichen/,
/secrets/b1/, /secrets/notplants-orchestrator/
/secrets/files/ CROSS-PROJECT files (0600) SYMLINKED from the fixed path a
third party insists on: ~/.ssh keys, a systemd
EnvironmentFile, nix authKeyFile, a TLS keypair
~/.config/sops/age/keys.txt the age private key, 0600
One home per secret: a value is in the store OR a file in /secrets/files, never both.
PROJECT SECRETS GO IN /secrets/<project>/ (operator, 2026-08-20). If a secret belongs to one
project, it goes in that project's directory — not in files/, and not with the project name
baked into the filename. `/secrets/lichen/test-pds.env`, not `/secrets/files/lichen-test-pds.env`.
Reserve files/ for things genuinely shared across projects.
WHY: a flat directory forces every name to carry its own scope, which nobody does consistently,
and then nobody can answer "what does this project hold?" or "what do I revoke if this project is
compromised?" without grepping. A directory answers both by listing. Put a README.md in the
project directory saying what each file is, what consumes it, and what breaks if it is lost —
the next person to read it will be doing so under time pressure.
The symlink rule is unchanged and applies the same way: a consumer that insists on a fixed path
gets a SYMLINK into /secrets/<project>/, so the file still exists exactly once.
One home per secret: a value is in the store OR a file under /secrets, never both.
/secrets is outside every git tree — not a repo, no remote — and outside /srv, which agents
grep and walk constantly.