Shell snapshots can trigger credential prompts and persist secret env vars in plaintext

Open 💬 5 comments Opened Jul 3, 2026 by swlee3108-hash

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 Desktop 26.623.101652 (com.openai.codex, build 4674)
  • 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 checkout 44aa8c4; searching for OP_SERVICE_ACCOUNT_TOKEN, op-sa, /opt/homebrew/bin/op, op read, and shell_snapshot found no LazyCodex implementation that owns shell snapshot creation or 1Password invocation.
  • Upstream Codex source evidence from /tmp/openai-codex-source: current upstream checkout da4c8ca; codex-rs/features/src/lib.rs marks shell_snapshot stable and default-enabled; codex-rs/core/src/shell_snapshot.rs creates the files, sources zsh startup files, and captures exports; runtime execution then sources the snapshot before shell commands.

Reproduction

  1. 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 .zshrc line that invokes a credential helper such as op read ....
  2. Start Codex Desktop or run a local Codex shell command in a project using the default shell snapshot feature.
  3. Inspect ~/.codex/shell_snapshots/*.sh while 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: op initially resolved to /opt/homebrew/bin/op; local shell startup contained export OPENAI_API_KEY=$(op read ...), which explains the unexpected 1Password prompt when Codex starts or snapshots a shell. Guarding bare op and changing startup/scripts to op-sa stopped the prompt path.
  • Runtime evidence: after redaction, the local system still had shell snapshot files under ~/.codex/shell_snapshots, but no remaining long ops_... token patterns were found by a targeted scan.
  • Source evidence: codex-rs/features/src/lib.rs:831-835 defines Feature::ShellSnapshot with key shell_snapshot, stage Stable, and default_enabled: true.
  • Source evidence: codex-rs/core/src/shell_snapshot.rs:43-47 defines three-day retention and only excludes PWD and OLDPWD from exports.
  • Source evidence: codex-rs/core/src/shell_snapshot.rs:320-325 sources ~/.zshrc/$ZDOTDIR/.zshrc before 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, and 438-466 capture exported variables via export -p, declare -xp, or env and write them into the snapshot.
  • Source evidence: codex-rs/core/src/shell_snapshot.rs:219-222 writes the captured snapshot to disk.
  • Source evidence: codex-rs/core/src/tools/runtimes/mod.rs:281-318 rewrites shell commands to source the snapshot file before executing the requested command.
  • Source evidence: codex-rs/core/src/tools/runtimes/mod_tests.rs:1069-1110 includes 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.rs proving 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, and EXAMPLE_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 .zshrc during startup/shell command execution.

---
This issue or PR was generated by LazyCodex.
Tag: lazycodex-generated

View original on GitHub ↗

5 Comments

rpelevin · 1 month ago

This looks like a shell-snapshot trust-boundary bug, not only a local dotfile hygiene issue.

The invariant I would want is:

  • snapshot creation must not execute interactive credential helpers by default
  • snapshot files must never persist raw secret-like values
  • replayed shell state should be enough for PATH/functions/aliases, not a full credential export
  • any omitted or redacted variable should produce a non-secret redaction receipt so users can debug missing env without seeing the secret
  • tool execution should source only a sanitized snapshot whose policy and source digest are recorded

A safe contract could split the shell snapshot into two records:

  • replayable environment: allowlisted values needed for command compatibility
  • non-replayable sensitive state: variable names and redaction reasons only, no values

Tests I would add:

  • zsh startup files containing credential-helper commands do not run during snapshot creation in non-interactive mode
  • OPENAI_API_KEY, OP_SERVICE_ACCOUNT_TOKEN, and _TOKEN / _SECRET / *_PASSWORD values never appear in snapshot files
  • snapshot permissions are owner-only and retention cleanup removes expired files
  • command replay still preserves non-secret PATH and expected shell behavior
  • redaction events are visible without leaking values
  • session logs and transcripts never receive raw snapshot contents

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.

1837620622 · 1 month ago

Ready-made minimal patch (secret export redaction)

This is the same root cause as documented here (EXCLUDED_EXPORT_VARS = PWD/OLDPWD only).

I implemented the export denylist/redaction half of the proposed fix (exact names + *_TOKEN/*_SECRET/*_PASSWORD/*_API_KEY patterns 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 | cc466b9 fix(core): redact secret env vars from shell snapshots |
| Author | @1837620622 — 35034498+1837620622@users.noreply.github.com |

Scope of this patch

  • Redacts secret-bearing env names/values from snapshot files
  • Does not yet change sourcing of interactive .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.

1837620622 · 1 month ago

Proposed fix

I pushed a defensive fix that redacts secret-bearing exports from shell snapshots (zsh/bash/sh/PowerShell):

I attempted to open a PR via API but this account currently gets a permissions error on CreatePullRequest for openai/codex. Happy to adjust the denylist if maintainers prefer an allowlist model.

Fixes the plaintext persistence path described in this issue.

1837620622 · 1 month ago

Secret persistence half: confirmed + fix on fork

On main, EXCLUDED_EXPORT_VARS is only PWD / OLDPWD in codex-rs/core/src/shell_snapshot.rs. Snapshot scripts source user rc files then capture export -p / env into $CODEX_HOME/shell_snapshots/*, so common credential names land on disk in plaintext.

Fix approach (name-based denylist)

  • Suffix/prefix ERE: *_TOKEN, *_SECRET, *_PASSWORD, *_API_KEY, narrow AWS_* / AZURE_* credential prefixes, …
  • Small exact list for bare names patterns miss: API_KEY, PGPASSWORD, MYSQL_PWD, OP_SESSION, DATABASE_URL, …
  • Same denylist across zsh/bash/sh/PowerShell scripts
  • No outer ^(…)$ wrapper on the combined alternation (would break prefix patterns like ^AWS_SECRET)

Residual (intentionally out of scope)

  • Does not stop interactive credential-helper prompts when sourcing .zshrc / op read
  • Name-based only (no value scanning)
  • PowerShell snapshot write path still early-bails as unsupported; script denylist is aligned for when enabled

Validation

cargo test -p codex-core --lib shell_snapshot
# 16 passed (macOS)

Branch / compare (cannot open PR: invitation-only + CreatePullRequest denied)

Happy to open a PR if invited for the secret-persistence half of this issue.

2vanm · 1 month ago

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_snapshot feature 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:

  • Disable the feature in Codex config — [features] → shell_snapshot = false (verified stable on codex-cli 0.139.0; the snapshot directory stays empty across real sessions).
  • Independently, scrub the child environment at the spawn boundary so the secrets never reach the snapshot code path at all (our harness clears the sensitive keys from the app-server's environment before launch).

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.