Amends 0009 rule 3 (“a human sets, rotates and deletes every
value”). Numbered after the highest ADR in flight (0256); the ADR steward reconciles the number
and the status.
Context
ADR 0009 makes a human set every secret value. In practice the owner hands over values (a GitHub App key, an App ID) by pasting them somewhere an agent can reach, which is exactly the leak 0009 exists to prevent: the value lands in a transcript. The owner decided (2026-10-20) that a dedicated agent may write values into Infisical for every environment, provided no one, the agent included, ever sees them. Hooks cannot reliably tell one agent from another, so the guarantee has to live in the tool, not in who is allowed to call it.Decision
- Invariant: nobody reads a value. A value is set by a human, or by the secret-manager flow below. Rule 3 of 0009 becomes: a human or the secret-manager flow sets a value; nobody reads one.
- One inbox. The owner drops one file per secret into
~/Secrets-inbox/(outside every repository, never synced). File name = secret name (SCREAMING_SNAKE, one extension stripped). Optional<NAME>.target.yamlwithenv(defaultdev) andpath(default/agents). Regular files only, at most 64 KB. A non-file value is a file holding just that value; there is no--valueflag (shell history). - One audited script.
pnpm secrets:import(tools/scripts/secrets-import.mjs) is the only writer. It never prints, logs or passes a value on argv (it writesNAME=@<0600 temp file>; the temp file sits in a dot-folder inside the inbox, is removed at once, on SIGINT/SIGTERM, and at the next start; the CLI’s support forNAME=@fileis checked before any write), captures and classifies all CLI output into fixed phrases, verifies by SHA-256 read-back in-process, deletes the inbox file on success, keeps it on failure, and appends a names-only line to~/.stackra/secrets-audit.log. Its core is callable asimportSecret({ name, env, path, valueStream, actor })returning only{ name, ok, reason }, so a future local request page reuses it. - Dry run first; production on the owner’s yes.
--dry-runvalidates name, target and file, reaches Infisical and reports would-overwrite. Forprodit prints a one-time token (HMAC under a per-machine key over name, env, path, value hash and time; valid 30 minutes). The import refusesprodwithout a valid, unused token, and the token is spent (recorded in the audit log) before the write starts, so a crash cannot make it reusable. The CLI token is a process rule, not an enforced yes: it is printed to the same caller that redeems it, so it proves a dry run happened and binds the exact file, while the owner’s yes depends on the agent following its instructions (show the dry-run line, ask, wait). The enforced owner-yes path is the local secret request page (STA-187), where the owner’s own submit is the yes. - The
secret-manageragent (.agents/agents/secret-manager.md, executor tier) runs onlypnpm secrets:importand reports names and results. Any caller may run the script; it is safe for all of them, because it never outputs a value. - Hooks are an accident guard, not a security boundary. For every agent they deny a shell command that names
Secrets-inboxor the confirm key (literally or by a glob such asSecrets-inbo?) or recursively reads the home directory, and Read, Grep (path andglob), Glob, Write and Edit that reach the inbox. A determined process can still get around them (an interpreter that builds the path at runtime). The guarantee rests on two things: the script never outputs a value, and agents are instructed never to read the inbox. - Runtime-store (Encore) secrets join the same flow once a tool for that store exists; until then 0009 stands for them.
Consequences
- The owner never pastes a value into a chat; the hand-over is a file drop plus a yes for production.
- Production confirmation through the CLI is only as strong as the agent’s discipline; when that matters, the owner uses the request page (STA-187).
- A compromised or careless agent can still write a wrong value into a store, but never read one; the audit log and the production confirmation bound that.
- The confirm key and audit log under
~/.stackra/are machine-local; a second machine has its own. - A new store (Encore, Workers, EAS) needs its own writer added to the script before the flow covers it.
Enforcement
tools/scripts/secrets-import.mjsandtools/scripts/__tests__/secrets-import.test.mjs: every path asserts no random canary value reaches output, the audit log or the runner’s argv; production refuses without a valid, unused, matching token..agents/hooks/guard-bash.mjs,.agents/hooks/guard-paths.mjs(matcherWrite|Edit|MultiEdit|NotebookEdit|Read|Grep|Globin.claude/settings.json) and.opencode/plugins/guard/decide.ts, with their tests.- Not enforced: the owner’s yes on the CLI path (a process rule; enforced on the request page, STA-187), and agents not reading the inbox by means the hooks do not recognise.
- Planned: hooks that know the calling agent’s identity could narrow the script to
secret-manager; not needed for the guarantee.
Supersedes and sources
Supersedes: none (amends 0009 rule 3). Source: owner decision “Secret-manager agent”, 2026-10-20; STA-185.