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 (a bash -c '...KEY=...' subshell, an inline curl -H "Authorization: Bearer <value>", etc.).
  • Unlike a one-time transcript leak, this is a standing exposure: the secret is readable via ps by 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

  1. Identify the exact secret (key NAME only) and its canonical SOPS home (CLAUDE.md §Secrets → SOPS file map).
  2. 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 read it from there instead of holding it in an argv-visible shell variable; delete the file on exit.
    • Real environment, not -c string: exporting into the process’s actual environment (rather than interpolating into the bash -c '...' argument) is visible only via /proc/<pid>/environ — readable by the owning user + root only, a materially smaller exposure than world-readable ps output. 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 cmd on it and assert no known secret-prefix pattern (sb_secret_, ghp_, sk-ant-, etc.) appears.
  3. 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.sh hook does not currently guard ps -o cmd/ps aux/ps -ef the way it guards docker inspect .Config.Env (#5165) and docker exec ... env (#5165) — a ps call against a locally-spawned process is a plausible future leak vector of the exact same class and is not yet hook-enforced. Treat any ps -o cmd/args/aux/ef command (local or over SSH) as requiring the same caution as a docker inspect/env dump: prefer ps -o pid,etime,stat,comm (no argv column) for a liveness check, and only fall back to a full-argv ps when 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 -c string) — 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.