The strangest part of issue #2847 isn’t that it’s open. It’s that it’s the third open issue asking for the same thing. #85 opened in April 2025. #1397 opened in June. Then #2847 in late August. #1397 was closed as a duplicate of #2847 – and #2847 is still sitting there, unresolved.
Meanwhile, your .env file is one rg AWS_SECRET away from being sent to a model. So if you came here looking for the OpenAI Codex exclude sensitive files fix and a tidy .codexignore template, I have bad news and a workaround.
Why .gitignore doesn’t save you
This is the part everyone gets wrong. Turns out, when Codex runs commands like rg, cat, or workspace-wide searches, ignored files can still be read locally – and their contents can appear in Codex responses. That’s the actual risk for .env files, credentials, or local configs sitting in the workspace. (Source: GitHub discussion #5523.)
Codex doesn’t pre-filter by .gitignore. The agent is a shell user. If you can cat the file, so can it. The maintainers’ position, per the same discussion: ignored files are not a read barrier. Sandbox permission profiles are the right layer to prevent cat, rg, test logs, or accidental summarization from exposing sensitive files.
The actual workaround: deny-read in config.toml
Here’s what most tutorials skip. Codex CLI (v0.125 and later – check your installed version, as this may have changed) supports a permissions.<name>.filesystem table where you can set an access level of "none" for specific paths or globs. Per a community deep-dive on Codex CLI filesystem security, this lets you define named permission profiles with path-level access control, and "none" denies read access entirely for matching paths or glob patterns.
Open ~/.codex/config.toml and add this:
# ~/.codex/config.toml
sandbox_mode = "workspace-write"
approval_policy = "on-request"
default_permissions = "hardened"
[sandbox_workspace_write]
exclude_slash_tmp = true
exclude_tmpdir_env_var = true
[permissions.hardened.filesystem]
":project_roots" = { "." = "write", "**/*.env" = "none", "**/.env.*" = "none", "**/*.pem" = "none", "**/*.key" = "none", "**/secrets.*" = "none" }
"/Users/you/.ssh" = "none"
"/Users/you/.aws" = "none"
"/Users/you/.config/gh" = "none"
The :project_roots token scopes rules relative to detected project roots (per the official config reference), so **/*.env applies inside whatever repo you’re in. Absolute paths like /Users/you/.ssh apply everywhere, regardless of working directory.
exclude_slash_tmp = true matters more than it looks. Setting it prevents the agent from using /tmp as a staging area for exfiltrated data – a technique observed in sandbox escape research, documented in the official config reference. Most tutorials treat it as a minor flag. It isn’t.
Now run codex and ask it to read .env. It should refuse at the sandbox layer, not just politely decline.
The nuclear option: bwrap the secrets away
If you don’t trust config files (fair), there’s a Linux-flavored move from the same GitHub discussion. Wrap the entire Codex process in bubblewrap with a tmpfs over your .env:
bwrap --ro-bind / / --tmpfs $PWD/.env --chdir "$PWD" codex run
What bwrap does here: it mounts an empty tmpfs over the .env path before Codex starts. From Codex’s perspective the file doesn’t exist. No glob, no rg, no cat can recover it. The whole filesystem is read-only except the working directory.
It’s ugly. It works. And it doesn’t depend on any config flag being honored correctly – which matters, because the next section is about config flags not being honored correctly.
Common pitfalls (the ones tutorials skip)
Before you trust any rule: After editing your config, verify which sandbox Codex is actually running. Check your active config file and confirm the printed sandbox mode matches what you set. Users have reported
sandbox_modeset under[profiles.*]tables silently falling back to read-only at session start (GitHub issue #2384). If there’s a mismatch, stop and fix it – don’t assume the rule is active.
1. MCP servers bypass your sandbox. The Codex sandbox protects Codex-managed local commands, not every external service you connect. Docs say the boundary is clear – practice shows people keep forgetting it applies only to Codex’s own shell commands. A filesystem MCP plugged in for “convenience” will happily read whatever you just denied at the Codex layer. (Source: GitHub discussion #5523.)
2. macOS Seatbelt and unbounded globs. On macOS specifically, Seatbelt snapshots glob matches before sandbox startup – which means unbounded ** patterns must be pre-expanded or they silently match nothing. A **/*.env that works on Linux can fail quietly on a Mac. Test with a known dummy file before trusting the rule.
3. Project-local config only loads if the project is trusted. Personal defaults live in ~/.codex/config.toml. Project overrides live in .codex/config.toml – but per the official config docs, Codex loads project-level config only when you’ve accepted the trust prompt for that project. If you haven’t, your repo-level deny rules aren’t active.
Does it actually work? A quick sanity test
Drop a fake secret in your repo:
echo "AWS_SECRET_ACCESS_KEY=test123fake" > .env
codex
Then ask: “What AWS credentials are in this project?”
Without the deny-read rules, Codex will cat .env or rg AWS and read it back to you. With the rules above, the sandboxed command should fail or return nothing – and you’ll see the failure in the tool output, not a polite refusal from the model. That distinction matters. A model refusal is a guess. A sandbox refusal is a guarantee.
When not to bother
Honestly? If you’re running Codex inside a disposable container, a fresh dev VM, or a GitHub Actions runner with no real secrets mounted, this is overkill. The sandbox is for protecting your host machine. Per the official sandboxing docs, danger-full-access removes filesystem and network boundaries and is intended for cases where you want Codex to act with full access – inside an ephemeral container, that’s often fine.
The deny-read setup pays off when:
- You run Codex on your laptop, where
~/.awsand~/.sshare real. - You work in a monorepo with mixed secret hygiene (some
.env.local, some.env.production.sample). - Your team can’t agree on a single secret manager and you’ve given up trying.
If none of those apply, leave the defaults alone and revisit this when #2847 ships a real .codexignore.
FAQ
Is there an official .codexignore file yet?
No. As of the issue’s August 2025 update, a comparable feature does not appear to exist in codex-rs, and the issue remains open. The deny-read permissions table is the closest thing.
Can I just block .env files with one line?
You can pass a permissions override directly on the command line for a one-off run – the syntax varies by version, so check your current CLI help output. It works for a single session but breaks the moment you forget to type it. Putting it in ~/.codex/config.toml means you can’t forget. For a team-wide policy, commit a .codex/config.toml to the repo and accept the trust prompt once.
What happens if Codex tries to read a denied file anyway?
The shell command returns a permission error – same as if the file didn’t exist. The file contents never enter the model’s context. One warning: a misconfigured rule that silently allows the read is worse than no rule at all, because it creates false confidence. Always test with a dummy secret first.
Next step: open ~/.codex/config.toml right now, paste the hardened block above, swap /Users/you for your actual home path, then run the echo .env + codex test. If it leaks, you’ll know in 30 seconds.