Playbook: Secret Exposure via Process Argv (ps)
Status: live · Origin: discovered 2026-08-18 during #5030 live validation, root-cause fix pr #6401, rotation incident pr #6402
Trigger
- Any command that lists another process’s full command line —
ps -o cmd,ps -o args,ps aux,ps -ef,ps -p <pid> -o cmd— run against a process (local or over SSH to any VPS/BMS host) that was started with a secret value interpolated directly into its command-line string (abash -c '...KEY=...'subshell, an inlinecurl -H "Authorization: Bearer <value>", etc.). - Unlike a one-time transcript leak, this is a standing exposure: the secret is readable via
psby any local user/process for the entire lifetime of the exposed process, not just at the moment someone happens to query it.
Confirm
# Does the process's own argv contain a credential-shaped string? (name only, do not print)
ps -p <pid> -o cmd | grep -qiE '(api[_-]?key|token|password|secret|_key)=' && echo "ARGV EXPOSURE" || echo "clean"If it matches: treat the value as compromised immediately — the fact that this Claude session happened to look is incidental; the exposure existed before and continues after unless fixed.
Fix — for the specific instance found
- Identify the exact secret (key NAME only) and its canonical SOPS home (
CLAUDE.md§Secrets → SOPS file map). - File a dev-coder hardening issue (
/new-issue) to stop embedding the value in the process’s command-line string. Two safe patterns, in order of preference:- File-based: write the value to a short-lived, mode-0600, per-job file owned by the
process’s own user; have the process
readit from there instead of holding it in an argv-visible shell variable; delete the file on exit. - Real environment, not
-cstring:exporting into the process’s actual environment (rather than interpolating into thebash -c '...'argument) is visible only via/proc/<pid>/environ— readable by the owning user + root only, a materially smaller exposure than world-readablepsoutput. Acceptable when file-based isolation isn’t available, not acceptable if other unprivileged local users share the host. - Add a regression check: after spawning a representative process,
ps -o cmdon it and assert no known secret-prefix pattern (sb_secret_,ghp_,sk-ant-, etc.) appears.
- File-based: write the value to a short-lived, mode-0600, per-job file owned by the
process’s own user; have the process
- Do not attempt the code fix from a
secret-manager-role session — that’s dev-coder scope.
Fix — rotation of the exposed value
Follow docs/playbooks/static-api-key-incident-rotation.md in full, starting at Step 0c (root
cause is already known here — cite this playbook in the --reason). Delegate to secret-manager
role (Class A: background Agent() reading roles/secret-manager.md; Class B: queue a
secret-manager request issue) — never rotate inline from a sys-admin/investigation session.
Known instance: SUPABASE_SERVICE_ROLE_KEY (aliased SUPA_HB_KEY/HB_KEY in the
queue-dispatcher’s worker heartbeat subshell, scripts/queue-dispatcher-loop.py /
scripts/spawn-worker.sh) was found exposed this way on 2026-08-18. Rotation is currently
blocked upstream at Supabase (#4626 — Management API mints a key that then 401s against the
project’s own data API, a platform-side control-plane/data-plane desync, reproduced 6 times across
#4583 and this incident). Do not re-attempt rotation of this specific key until Supabase resolves
#4626 — check that issue’s latest comment before trying again.
Escalation
If the exposed key cannot be rotated (provider-blocked, as above) and the argv-exposure code fix
is not yet deployed: this is a live, standing exposure with no full mitigation available. File/
update a human-action-labeled tracking issue noting both blockers explicitly, rather than letting
it go undocumented — do not silently accept the risk.
Prevention
- The
pre-bash-safety.shhook does not currently guardps -o cmd/ps aux/ps -efthe way it guardsdocker inspect .Config.Env(#5165) anddocker exec ... env(#5165) — apscall against a locally-spawned process is a plausible future leak vector of the exact same class and is not yet hook-enforced. Treat anyps -o cmd/args/aux/efcommand (local or over SSH) as requiring the same caution as adocker inspect/envdump: preferps -o pid,etime,stat,comm(no argv column) for a liveness check, and only fall back to a full-argvpswhen genuinely necessary — never on a process you know or suspect was spawned with an inline secret. - Root cause is a code-level anti-pattern (interpolating a secret into a
bash -cstring) — the most durable prevention is fixing every spawn path that does this, not just guarding the read side. Grep the codebase periodically for `bash -c ’.*=$[A-Z_]*KEY’ style patterns during security audits.