[FEATURE] Credential guardrail should recognise operator-owned secret moves and stay consistent in-session
Summary
The credential guardrail refuses to place a secret (key, token, private key) into a field or env var even when it's my own secret, going into my own infrastructure, through a command that never prints the value. For a single operator managing self-hosted infra, this blocks routine secret management, and the model has no way to tell it apart from actually leaking a secret. It also fires inconsistently within one session, which is what makes it read as arbitrary rather than principled.
Concrete scenario
I was standing up a self-hosted service on my own Railway project (single operator, my own account, my own infra). During setup, Claude Code did the following without issue: it set my non-secret config (account topology, a public client ID, redirect URIs), and it read my database URL and auth token into shell variables to run verification calls. Then it refused to set the one secret value (a client secret, or a service-account private key), even though the command it would run reads straight from a local file and never surfaces the value:
railway variables --set "GOOGLE_SHEETS_SA_PRIVATE_KEY=$(jq -r .private_key key.json)"
The value goes from a local file to my own service. It never appears in the chat, the transcript, or tool output. There is no third party and no exfiltration path. The guardrail treats this the same as typing a secret into an untrusted form, and the only remaining fallback is me hand-couriering the value between the file and the dashboard, which is the least secure option in the whole loop.
What I'd like
Give the guardrail a way to recognise operator-owned secret moves as a distinct, allowed case: a value that goes from a local source I control to infrastructure I control, via a command that never prints it. Concretely:
- Distinguish "move my own secret into my own infra via a never-printed command" from "enter a secret into a third party's field". The current rule collapses both into one refusal.
- Be consistent within a session. Today the model will set some credential-adjacent values (config, public IDs) and read others (database URLs, tokens) but balk at the secret, with no visible principle separating them. Whatever the line is, apply it the same way to all of them.
- If a hard block has to stay, make it predictable: state the boundary at the start of the task, not when we hit it mid-flow. The mid-task surprise is most of the friction.
Why this is distinct from #77084
#77084 (secret lockbox with broker injection) covers the same problem space, but proposes building a synced secret store where the agent uses secrets without ever reading them. That is a larger piece of infrastructure, and it accepts the block as correct. This request is narrower: recalibrate the existing guardrail to recognise operator-owned moves, and make its behaviour consistent. The two are complementary. A lockbox is one way to satisfy this, but a calibration fix would not require the whole store.
Related
- #77084 (lockbox / broker injection; same problem, different proposal)
- #76652 (closed; over-sensitive classification on secret-management work)
Environment
- Claude Code, macOS. Model: Opus 4.8.