OpenAI Codex secret management: keeping keys out of the CLI agent
OpenAI Codex is a CLI agent: it reads your repository, runs commands, and calls model APIs to get work done. To do that work it needs credentials — for the model API itself, and for the tools and services in your project. The default path is the path of least resistance: keys in environment variables, tokens in .env files, credentials in config. That path ends with secrets inside the context window. This article is about the alternative: keeping keys out of the agent while keeping the agent productive.
Where Codex picks up secrets
The routes are the same as for any CLI agent. Environment variables:
whatever the agent’s shell has, the agent has. .env files: agents load these
eagerly, because that is how projects configure themselves. Config files:
.npmrc, .pypirc, cloud CLI configs, and similar files carry tokens in
plaintext. And tool output: test runs, build logs, and CLI invocations echo
environment values back to stdout, which becomes part of the transcript.
None of this requires a mistake. An agent that does its job reads the project, and the project’s secrets travel with it.
Why the context window is the wrong home for keys
Three reasons, in increasing order of severity. The context is sent to a model API: with Codex, your secrets transit an external service as part of ordinary operation. The context is persisted: session transcripts store what the agent saw, and transcript files are rarely treated like credential stores. And the context is an attack surface: prompt injection is a practical threat for agents that read fetched or pasted content, and every secret in context is a secret that can be requested and exfiltrated in a single response. The more keys the agent carries, the larger the blast radius of one bad fetch.
Secret management patterns that work
The pattern that works with CLI agents is the one that works with any untrusted caller: give access, not keys. Concretely:
- Inject at spawn, not at export. Wrap the agent invocation so secrets are resolved into the subprocess environment at launch, and sanitize stdout and stderr — including base64 and URL-encoded variants — so values never appear in the transcript.
- Scope by invocation. A per-run injection means a token exists for the duration of the run, not for the lifetime of a shell.
- Prefer short-lived credentials. OAuth device flow lets the agent authenticate without ever handling the refresh token: single-use rotation means a stolen token is already spent, and an invalid grant is never retried, so a revoked credential fails closed instead of looping.
- Log access, not values. An audit trail that records which secret was used and when — without recording the secret — turns post-incident investigation from archaeology into a lookup.
A concrete setup
A workable setup for a Codex workflow looks like this: keep the store where it already is (pass or Bitwarden), never export secrets into the shell, and wrap agent runs with a broker that injects credentials and filters what the agent emits and sends. The agent calls the model API through a local DLP reverse proxy that masks secret patterns outbound, and session databases are scrubbed on a schedule, with dry-run and backup defaults so cleanup never destroys data it should not.
trustless implements exactly this:
trustless run wraps command execution with injection and sanitization,
trustless serve proxies any OpenAI-compatible endpoint with per-host
injection and DLP, and trustless oauth handles device-flow tokens for
Google and Lark — all reading your existing store, with no migration and no
new vault format. The commands documentation walks
through each one.
If your Codex sessions were inspected tomorrow, would any of them contain a production key? What would you need to change to answer no?