.env 里的东西一旦被 agent 读进上下文,就可能出现在它的回复、日志,甚至它新写的测试用例里。而让它在仓库里修 bug、跑测试时,为了弄清数据库怎么连,打开 .env 看一眼是很顺手的一步。在 CLAUDE.md 或 AGENTS.md 里写一句「别碰 .env」只算提醒,能真正拦住的是工具自己的权限规则和系统层面的沙箱。CLAUDE.md 为什么只是建议、hooks 怎么做硬拦截,站里另有帖子聊,这里只说密钥这一块,下面的内容按 10 月的官方文档整理。
Claude Code:权限规则和沙箱各管一半
权限规则写在 .claude/settings.json 的 permissions.deny 里,settings 文档和 permissions 文档给的写法是这样:
{
"permissions": {
"deny": ["Read(./.env)", "Read(./.env.*)", "Read(./secrets/**)"]
}
}
规则本身有几处要注意:
- 规则按 deny、ask、allow 的顺序判断,deny 命中就定了,写得再具体的 allow 也开不出例外。
- Read 的 deny 同时挡住 Edit 和 Write 对同一路径的操作。
- 项目里如果还留着
.claudeignore,它已经不起作用,条目要搬进 Read deny 规则。 - 裸文件名按 gitignore 语法匹配任意深度,
Read(.env)和Read(**/.env)等价,但只管当前目录往下。
容易误会的是 Bash。文档写明,Read/Edit 的 deny 也作用于它认得的文件命令(cat、head、tail、sed、tee)和 < file 这类重定向,所以 cat .env 是会被拦的。管不到的是不点名文件的命令,像在那个目录下直接 grep -r,以及自己去打开文件的子进程,一段 Python 或 Node 脚本就能绕过去。官方的意思是,要让所有进程都读不到某个路径,得开沙箱。
沙箱默认是关的,用 /sandbox 或者 sandbox.enabled 打开。它默认允许读机器上的大部分位置,~/.ssh、~/.aws/credentials 都在内,环境变量也会原样继承,所以要自己声明:
{
"sandbox": {
"enabled": true,
"allowUnsandboxedCommands": false,
"credentials": {
"files": [{ "path": "~/.aws/credentials", "mode": "deny" }],
"envVars": [{ "name": "GITHUB_TOKEN", "mode": "deny" }]
}
}
}
项目里的 .env 用 sandbox.filesystem.denyRead 列进去,相对路径怎么解析看文档里路径前缀那一节。allowUnsandboxedCommands: false 是不让 Claude 在命令失败后申请脱离沙箱重跑。但沙箱只包 shell 命令,内置的 Read 工具不受 denyRead 约束,所以两层要一起配。想让所有子进程都拿不到凭据类环境变量,文档还提供了 CLAUDE_CODE_SUBPROCESS_ENV_SCRUB。
Codex:默认沙箱管写和联网,不管读
按 Agent approvals & security 的说法,CLI 和 IDE 扩展在版本库里默认走 workspace-write 加 on-request:能读文件、在工作区里改、跑命令,网络默认关。read-only 也只是不让改。也就是说仓库里的 .env 默认是读得到的,关网只能减少读到之后往外发的机会。
要挡读,得用 permission profiles,文档标着 Beta:
default_permissions = "project-edit"
[permissions.project-edit]
extends = ":workspace"
[permissions.project-edit.filesystem.":workspace_roots"]
"**/*.env" = "deny"
**/*.env 能不能匹配到名字就叫 .env 的文件,最好别想当然,保险起见可以再加一行 "**/.env" = "deny",配完用下面那条命令实测。
还有两个坑。profile 和老的 sandbox_mode、[sandbox_workspace_write] 不能混用,任何一层配置里出现 sandbox_mode 或者启动时带了 --sandbox,Codex 就改用老设置,profile 不生效。另外在 Linux、WSL 和 Windows 上,deny 的 glob 是在每个沙箱进程启动时展开的,之后新建的匹配文件,已经在跑的长进程(dev server、watcher)照样能读。配完可以用 codex sandbox linux --permissions-profile project-edit cat .env 试一下,macOS 把 linux 换成 macos。
环境变量走 shell_environment_policy。ignore_default_excludes 默认是 true,意思是名字里带 KEY、SECRET、TOKEN 的变量默认会保留下来,想自动剔掉要显式设成 false:
[shell_environment_policy]
inherit = "core"
ignore_default_excludes = false
[shell_environment_policy.filters]
"AWS_*" = "exclude"
工具之外
- 真正的生产密钥不要放在工作区里,本地
.env只放开发库和测试环境的值。 - 需要真 key 的步骤,运行时从密钥管理服务或 CI 的环境注入里拿,不必先落成文件。
- 给开发和 agent 用的 key 尽量短期、限定范围,只读、只能碰测试项目,漏了损失也有限。
.gitignore只决定提交不提交,跟 agent 能不能读没有关系。- 怀疑哪个 key 已经进过对话或日志,就先吊销换新,再去查用量和访问记录,删聊天记录不算处理完。
你们给 agent 用的 key 是单独发一套,还是直接共用开发环境那套?