Shell snapshots can trigger credential prompts and persist secret env vars in plaintext
Summary
Codex Desktop's local shell snapshot feature can source user shell startup files and then persist exported secret environment variables in plaintext under ~/.codex/shell_snapshots. In environments where .zshrc invokes a credential helper such as op read, starting or using Codex can also trigger unexpected biometric authentication prompts.
Environment
- LazyCodex version: 4.12.1
- Codex version:
codex-cli 0.142.5, Codex Desktop26.623.101652(com.openai.codex, build4674) - OS: macOS 26.3.1, arm64
- Install method: Codex Desktop app plus bundled CLI
- Relevant config: local shell snapshot feature is enabled by default in upstream Codex; zsh is the user shell; 1Password CLI version observed was
2.34.0
Repository Decision
- Target repository:
openai/codex - Why this belongs there: the behavior is implemented in upstream Codex core shell snapshot code and reproduces without LazyCodex-specific code paths.
- LazyCodex evidence (runtime +
/tmp/lazycodex-source): current LazyCodex checkout44aa8c4; searching forOP_SERVICE_ACCOUNT_TOKEN,op-sa,/opt/homebrew/bin/op,op read, andshell_snapshotfound no LazyCodex implementation that owns shell snapshot creation or 1Password invocation. - Upstream Codex source evidence from
/tmp/openai-codex-source: current upstream checkoutda4c8ca;codex-rs/features/src/lib.rsmarksshell_snapshotstable and default-enabled;codex-rs/core/src/shell_snapshot.rscreates the files, sources zsh startup files, and captures exports; runtime execution then sources the snapshot before shell commands.
Reproduction
- On macOS with zsh, configure a shell startup file with an exported secret-like value, for example
export CODEX_REPRO_SECRET=secret-value. To reproduce the biometric prompt variant, use a.zshrcline that invokes a credential helper such asop read .... - Start Codex Desktop or run a local Codex shell command in a project using the default shell snapshot feature.
- Inspect
~/.codex/shell_snapshots/*.shwhile the session is active, or observe shell snapshot behavior during command execution.
Expected Behavior
Codex should not run interactive credential helper commands from shell startup files in a way that surprises the user with biometric prompts. It also should not persist exported secrets such as API keys, access tokens, or service-account tokens in plaintext snapshot files or session artifacts.
Actual Behavior
The local run that triggered this report produced unexpected macOS 1Password biometric prompts while using Codex. Runtime investigation showed that direct op calls were reachable from the Codex shell environment and that shell snapshot artifacts had contained a 1Password service-account token matching the ops_... token shape before local redaction. After local cleanup, a broad scan for long ops_... token patterns across .codex, .claude, and the workspace returned zero matches.
Evidence
- Runtime evidence:
opinitially resolved to/opt/homebrew/bin/op; local shell startup containedexport OPENAI_API_KEY=$(op read ...), which explains the unexpected 1Password prompt when Codex starts or snapshots a shell. Guarding bareopand changing startup/scripts toop-sastopped the prompt path. - Runtime evidence: after redaction, the local system still had shell snapshot files under
~/.codex/shell_snapshots, but no remaining longops_...token patterns were found by a targeted scan. - Source evidence:
codex-rs/features/src/lib.rs:831-835definesFeature::ShellSnapshotwith keyshell_snapshot, stageStable, anddefault_enabled: true. - Source evidence:
codex-rs/core/src/shell_snapshot.rs:43-47defines three-day retention and only excludesPWDandOLDPWDfrom exports. - Source evidence:
codex-rs/core/src/shell_snapshot.rs:320-325sources~/.zshrc/$ZDOTDIR/.zshrcbefore writing a zsh snapshot, so startup commands such as credential helper reads can run during snapshot creation. - Source evidence:
codex-rs/core/src/shell_snapshot.rs:340-357,384-399, and438-466capture exported variables viaexport -p,declare -xp, orenvand write them into the snapshot. - Source evidence:
codex-rs/core/src/shell_snapshot.rs:219-222writes the captured snapshot to disk. - Source evidence:
codex-rs/core/src/tools/runtimes/mod.rs:281-318rewrites shell commands to source the snapshot file before executing the requested command. - Source evidence:
codex-rs/core/src/tools/runtimes/mod_tests.rs:1069-1110includes a test proving secret override values are kept out of argv but still passed through the shell environment; there is no analogous redaction for snapshot file contents.
Root Cause
The confirmed root cause is that shell snapshot creation treats exported environment variables as ordinary replayable shell state. For zsh, the snapshot script first sources user startup config, then serializes exported variables to a plaintext .sh file. The export filter only excludes PWD and OLDPWD, so variables with common secret names such as OPENAI_API_KEY, OP_SERVICE_ACCOUNT_TOKEN, *_TOKEN, *_SECRET, *_PASSWORD, and credential-helper-derived exports can be persisted.
The biometric prompt symptom is a second-order effect of the same path: sourcing .zshrc during snapshot creation can execute interactive credential-helper commands such as op read, causing macOS/1Password authentication UI to appear even though the user only interacted with Codex.
Proposed Fix
- Add a denylist/redaction layer for shell snapshot exports before writing files. At minimum redact or omit common secret-bearing names:
*_TOKEN,*_SECRET,*_PASSWORD,*_KEY,OPENAI_API_KEY,OP_SERVICE_ACCOUNT_TOKEN, cloud-provider credential variables, and similar patterns. - Consider a positive allowlist for snapshot exports instead of serializing all exported environment variables.
- Avoid sourcing full interactive startup files for snapshot creation, or provide an opt-out / non-interactive snapshot mode that cannot invoke credential helpers.
- Store snapshots with restrictive permissions and avoid copying raw snapshot contents into session logs or rollout artifacts.
- Add regression tests in
codex-rs/core/src/shell_snapshot_tests.rsproving secret-like env vars are omitted/redacted for zsh/bash/sh snapshots and that snapshot generation does not require executing credential-helper startup hooks.
Verification Plan
- Add tests that set
OPENAI_API_KEY,OP_SERVICE_ACCOUNT_TOKEN,EXAMPLE_TOKEN,EXAMPLE_SECRET, andEXAMPLE_PASSWORD; verify generated snapshots do not contain their values. - Add a zsh snapshot test with a controlled startup file that would emit or invoke a sentinel command; verify the new non-interactive mode avoids the prompt-triggering path or makes the behavior opt-in.
- Run existing shell snapshot tests and runtime shell execution tests for zsh/bash/sh to ensure PATH, aliases, functions, and expected non-secret exports still replay correctly.
- Manually verify on macOS that Codex no longer triggers 1Password Touch ID prompts from
.zshrcduring startup/shell command execution.
---
This issue or PR was generated by LazyCodex.
Tag: lazycodex-generated
5 Comments
This looks like a shell-snapshot trust-boundary bug, not only a local dotfile hygiene issue.
The invariant I would want is:
A safe contract could split the shell snapshot into two records:
Tests I would add:
That keeps shell replay useful while making the snapshot a no-secret, no-surprise-credential boundary.
Boundary: architecture and conformance feedback only; no claim about OpenAI/Codex alignment, implementation correctness, acceptance, partnership, integration, customer interest, official alignment, Codex usage, or Neura usage.
Ready-made minimal patch (secret export redaction)
This is the same root cause as documented here (
EXCLUDED_EXPORT_VARS = PWD/OLDPWDonly).I implemented the export denylist/redaction half of the proposed fix (exact names +
*_TOKEN/*_SECRET/*_PASSWORD/*_API_KEYpatterns for zsh/bash/sh/PowerShell) with regression tests.Because this repo only accepts PRs from collaborators, posting the branch for cherry-pick:
| Item | Link |
|------|------|
| Fork branch | https://github.com/1837620622/codex/tree/fix/shell-snapshot-redact-secrets |
| Compare vs main | https://github.com/openai/codex/compare/main...1837620622:codex:fix/shell-snapshot-redact-secrets |
| Commit |
cc466b9fix(core): redact secret env vars from shell snapshots|| Author | @1837620622 —
35034498+1837620622@users.noreply.github.com|Scope of this patch
.zshrc/ credential-helper prompts (second half of this issue)Happy for a collaborator to cherry-pick or invite a PR. I closed my later issue #32327 as a duplicate of this one.
Proposed fix
I pushed a defensive fix that redacts secret-bearing exports from shell snapshots (zsh/bash/sh/PowerShell):
3b93fc6— expands denylist (exact +*_TOKEN/*_SECRET/*_PASSWORD/*_API_KEYpatterns,DATABASE_URL,PGPASSWORD, …) and keeps operational vars likeAWS_REGIONreplayable.bash_snapshot_redacts_secret_exports+ ERE unit checks.I attempted to open a PR via API but this account currently gets a permissions error on
CreatePullRequestforopenai/codex. Happy to adjust the denylist if maintainers prefer an allowlist model.Fixes the plaintext persistence path described in this issue.
Secret persistence half: confirmed + fix on fork
On
main,EXCLUDED_EXPORT_VARSis onlyPWD/OLDPWDincodex-rs/core/src/shell_snapshot.rs. Snapshot scripts source user rc files then captureexport -p/ env into$CODEX_HOME/shell_snapshots/*, so common credential names land on disk in plaintext.Fix approach (name-based denylist)
*_TOKEN,*_SECRET,*_PASSWORD,*_API_KEY, narrowAWS_*/AZURE_*credential prefixes, …API_KEY,PGPASSWORD,MYSQL_PWD,OP_SESSION,DATABASE_URL, …^(…)$wrapper on the combined alternation (would break prefix patterns like^AWS_SECRET)Residual (intentionally out of scope)
.zshrc/op readValidation
Branch / compare (cannot open PR: invitation-only + CreatePullRequest denied)
Happy to open a PR if invited for the secret-persistence half of this issue.
Confirming this on a headless Linux server deployment (not just macOS Desktop), where the blast radius is larger than on an interactive workstation.
Setup: a self-hosted gateway spawns Codex-backed agent sessions on a schedule. At startup the gateway hydrates a set of service credentials (API keys / tokens for various integrations) into its own process environment, and those exported vars are inherited by the Codex app-server child process. With the default
shell_snapshotfeature enabled, Codex sourced the shell startup files and persisted the exported secrets in plaintext under~/.codex/shell_snapshots/*.sh.Why it matters more on a server: these snapshots sit on disk for the lifetime of the session (and beyond, until cleaned), readable by any same-user process. On an unattended host running many automated sessions, that becomes a persistent plaintext credential store nobody asked for — created silently, with no prompt and nothing in the logs. The biometric-prompt symptom that flags the problem on an interactive Mac simply never appears here, so the leak is invisible.
What fixed it for us, in case it helps others until there's a safer upstream default:
[features]→shell_snapshot = false(verified stable oncodex-cli 0.139.0; the snapshot directory stays empty across real sessions).Strong +1 on the expected behavior described here: secrets exported into the shell environment should not be persisted to plaintext session artifacts by default. A safe default — feature off, or redaction of secret-shaped values before they're written — would close this for headless deployments that can't rely on a human noticing a prompt.