AI agent 第一次改到 prod.config.ts、删掉 .env.local、或者”顺手修一下”某个 migration 文件之后,你就明白了:写在 CLAUDE.md 里的那句话是建议,不是围栏。写下这句话的模型,过三轮对话就忘了。这篇要把两件事分清楚——模型”试图”遵守的规则,和你的工具链”强制执行”的边界。然后给出截至 2026 年 6 月,Claude Code、Cursor、Codex 各自确切的配置写法。
TL;DR
- 写进
CLAUDE.md/AGENTS.md的规则只塑造意图,不强制任何东西。模型可以无视它;真正拦住工具调用的是 harness(执行框架)。 - 真正的强制执行在这几层:deny 规则(Claude Code 的
settings.json)、ignore 文件(.cursorignore、.codexignore)、沙盒、PreToolUse hook、以及服务端的分支保护 + push protection。 - 把它们叠起来:ignore 文件让 secret 进不了上下文,deny 规则拦住编辑,secret 扫描器兜住漏网的,分支保护让 agent 永远没法直接推
main。 - 不管单人还是团队,性价比最高的一步就是:一段 10 行的
permissions.deny,外加把gitleaks挂成 pre-commit hook。
这篇适合谁看
把 Cursor、Claude Code 或 Codex 的写权限放进真实 repo 的所有人——独立开发者、小团队,尤其是 repo 涉及 secret、基础设施、生产数据的人。如果你现在唯一的护栏是”我礼貌地拜托 agent”,那就从这里开始。
为什么”礼貌地拜托”会失败
Claude Code 的权限规则是由 harness 执行的,不是由模型执行。Anthropic 官方文档说得很直白:prompt 或 CLAUDE.md 里的指令”塑造 Claude 想做什么,但不改变 Claude Code 允许什么”。这一句话就说清了纯 prompt 护栏的全部毛病:
- agent 跨 session 会重置,口头规则不持久。
- 长上下文会退化——写在顶部的规则会被回放,埋在中间的规则会被丢掉。
- Cursor、Claude Code、Codex 各自尊重的约定不同,同一条规则换个工具就不通用。
- 就算规则在场,agent 也会给边界例外找理由(“我必须改这个才能修 bug”)。
- 背后没有执行机制的规则,是期望,不是护栏。
四层强制执行
按”越便宜越靠前”的顺序去想。每一层都兜住前一层漏掉的。
| 层 | 作用 | 写在哪 | 模型能绕过吗 |
|---|---|---|---|
| ignore 文件 | 把文件彻底从 agent 的上下文里拿掉 | .cursorignore、.codexignore、项目配置 | 不能——agent 根本看不到 |
| deny 规则 / allowlist | 在工具边界拦住编辑/命令 | Claude Code settings.json、Cursor allowlist | 不能——harness 强制执行 |
| 沙盒 | 在 OS 级别限制 shell 的文件系统/网络 | Claude Code 沙盒、Codex workspace-write | 不能——即使 prompt injection 钻了进来 |
| 分支保护 + push protection | 阻止改动落进 main | GitHub 服务端 | 不能——跑在服务端,不在本地 |
前两层阻止 agent”做出”坏改动;后两层阻止坏改动”落地”。两半都要。
Claude Code:settings.json 里的 deny 规则
Claude Code 按 deny → ask → allow 的顺序求值;第一个命中的赢,而且任何一层的 deny 都不会被更低层的 allow 覆盖。在 repo 根目录的 .claude/settings.json 里放一段 deny(提交进 repo,让全队都拿到):
{
"permissions": {
"deny": [
"Read(.env)",
"Read(.env.*)",
"Read(./src/lib/secrets/**)",
"Edit(.env)",
"Edit(.env.*)",
"Edit(/infra/**)",
"Edit(/migrations/**)",
"Edit(/prisma/schema.prisma)",
"Bash(git push --force *)",
"Bash(npm publish *)",
"Bash(rm -rf *)"
]
}
}
两个容易踩的点:
Read和Edit的模式遵循.gitignore语义,不是从根目录开始的 glob。裸写的Read(.env)等价于Read(**/.env),会匹配任意深度的.env。前导一个/锚定到项目根(Edit(/infra/**)),而前导两个//是唯一能写出”文件系统绝对路径”的方式。Read/Edit的 deny 规则也覆盖 Claude Code 认识的那几个 Bash 文件命令——cat、head、tail、sed——但不覆盖任意脚本(一个自己 open 文件的 Python 或 Node 程序)。要拦住那种情况,开启 OS 级沙盒(文档里的/en/sandboxing),它会把你的 deny 规则合并进真实的文件系统边界。
别掉进在真实 repo 上跑 --dangerously-skip-permissions(即 bypassPermissions 模式)的坑。它对大多数路径跳过提示;显式 deny 规则仍然生效,但你把所有交互式 checkpoint 都扔了。把它留给容器或虚拟机。管理员可以用 managed settings 里的 permissions.disableBypassPermissionsMode: "disable" 硬性禁用它。
如果你想整体跑得很快、但要绝对拦住少数几个命令:把 Bash 加进 allowlist,再挂一个以退出码 2 退出的 PreToolUse hook 来拒掉那几个命令——退出码 2 会在权限规则被求值之前就停掉这次调用。
Cursor:denylist 已经没了——改用 allowlist
如果你 2026 年之前给 Cursor 配过 Auto-Run 的 denylist,把它拆了。安全研究者(Backslash)找到了至少四种让被攻陷的 agent 绕过 denylist 执行命令的方式,Cursor 已经在 1.3 版本里弃用了 denylist。Cursor 3.6 及以上的现代默认是 Auto-review:它跑 allowlist 里的命令,能沙盒的就沙盒,剩下不认识的就交给一个 LLM 分类器,根据安全性、以及该调用和你声明意图的吻合程度,判定放行还是拦截。
实操:
- 把敏感路径加进 repo 根目录的
.cursorignore(语法和.gitignore一样)。里面的文件会被排除在索引和 agent 访问之外——上下文里没有,就改不到。 - 优先用安全命令的 allowlist(lint、test、typecheck),而不是危险命令的 denylist。denylist 想穷举每一条坏命令;allowlist 只点名那几条好命令,其余默认拦掉。
- 对所有不在 allowlist 里的命令保持终端审批开启。别第一天就把整个 repo 切进无人值守的 Run Mode。
Codex:选沙盒模式,而不是只靠 prompt
OpenAI 的 Codex CLI 靠沙盒模式加审批策略来强制安全,在 config.toml 里设,或会话中用 /permissions 切:
| 模式 | Codex 能做什么 | 什么时候用 |
|---|---|---|
read-only | 浏览文件;未经批准计划不编辑、不跑命令 | 审查、规划、摸不熟的 repo |
workspace-write(默认) | 读、在工作区内编辑、跑常规本地命令 | 日常本地开发 |
danger-full-access | 没有文件系统和网络边界 | 涉密 repo 绝不用;只在容器里 |
低摩擦默认是 workspace-write 配 on-request 审批:Codex 在沙盒内工作,要越界时才问你。“完全访问”(sandbox_mode = "danger-full-access" + approval_policy = "never")一次性移除所有边界——把它当 sudo 对待。用 .codexignore 把敏感路径挡在上下文之外。
写下来的规则这一层(仍然值得保留)
强制执行的层是围栏;写下来的规则是路牌,让 agent 一开始就不往围栏那边走。在 CLAUDE.md / AGENTS.md 顶部放一段具体的块——只写具体路径,绝不写”配置文件小心点”:
## agent 禁止编辑的文件
- .env, .env.*, .env.local, .env.production
- infra/**, terraform/**, k8s/**
- migrations/**
- src/lib/secrets/**
- prisma/schema.prisma (读可以,改必须人工 PR)
- package-lock.json (只允许 `npm install` 改)
- VERSION, CHANGELOG.md (仅人改)
## 禁止的操作
- git push --force, git push --force-with-lease
- 任何已推到 origin 的分支上的 git rebase
- npm publish, pnpm publish, yarn publish
- 项目根目录的任何 rm -rf
把这些路径同步进你的 deny 规则和 ignore 文件,让同一份清单既被”请求”也被”强制”。模糊的指导会变成玄学;具体路径才泛化得稳。
Secret 扫描:最后一道防线
一旦 secret 进了 commit,规则和 review 都救不了——你得扫。截至 2026 年 6 月,标准方案是两层:
gitleaks挂成 pre-commit hook,让带 secret 的提交在本地就被拦住。用pre-commit框架装——加一个指向github.com/gitleaks/gitleaks的.pre-commit-config.yaml,跑pre-commit install——或者直接gitleaks protect --install。两种方式都会在写入前扫描每一次 commit。- GitHub push protection,在服务端拦下含已知 secret 模式的 push,所以跳过本地 hook 也绕不过去。在 Settings → Code security → Secret scanning → Push protection 里开启。所有公开 repo 免费。
pre-commit 在提交前抓,push protection 在推到 GitHub 前抓。两个都装上,然后忘了它们。
分支保护:agent 永远推不到 main
在 main 上用 GitHub ruleset(或经典分支保护):必须 PR、必须过 status check、不许 force-push、至少一个人工 review。这一层让 agent 在结构上就没法推你的受保护分支——即使它想,即使其他每一层都失守。把 infra/、migrations/ 和安全敏感路径在 CODEOWNERS 里标出来,这样任何动到它们的 PR 都必须有人工 reviewer。
建议的操作流程
ignore 文件 + deny 规则 + 沙盒 + secret 扫描 hook(强制执行)→ 在干净 tree 上跑 agent → 审 diff → 人提交或批准 PR → 分支保护把住 push。在干净的 tree(没有未提交的本地改动)上跑 agent,这样跑完后 git status 里的全是 agent 的活,不掺你的。每一层都很便宜,叠在一起就能抓住上一层漏掉的。
FAQ
- 单人开发也要分支保护吗? 要。它还能拦住凌晨 2 点慌乱中 force-push 的你自己,而且这是唯一跑在服务端、本地没法绕的层。
CLAUDE.md里的规则真能拦住一次编辑吗? 不能。Anthropic 文档明确写着CLAUDE.md塑造 Claude 想做什么、而非 Claude Code 允许什么。每条写下来的规则都要配一条对应的deny规则或 ignore 文件条目。- 如果我就是想让 agent 改一个平时被保护的文件呢? 临时去掉那条
deny规则、做完、恢复规则,把两步分成两个独立 commit。不要”就这一次”地放宽规则。 - 我的 Cursor denylist 以前一直好用——为什么要换? 它在 Cursor 1.3 被弃用了,因为研究者找到了多个绕过方式。3.6+ 上改用 Auto-review 加 allowlist;denylist 是一场穷举每条危险命令的必败游戏。
- 能跑 shell 命令的 agent 怎么办? 用沙盒或容器加 allowlist。Codex 的
workspace-write模式和 Claude Code 的 OS 沙盒都能约束 shell。永远不让 agent 跑sudo、danger-full-access或生产部署命令。 - agent 还是改了被保护的文件——现在怎么办? secret 扫描 hook 或 push protection 应当已经拦下了任何 secret;如果落地的是非 secret 改动,
git revert那个 commit,并把路径同时加进你的deny规则和 ignore 文件,让它不会重演。
容易踩的坑
- 信口头那句”别动 X”——它一个 session 内就衰减;把它写成
deny规则。 - 1.3 之后还留着 Cursor 的 Auto-Run denylist——在 3.6+ 上换成 allowlist。
- 在涉密 repo 上跑
--dangerously-skip-permissions——把它留给一次性容器。 - 在脏 tree 上跑 agent——你的改动和 agent 的混在一起,review 全靠猜。
- 让 agent 直推——
main上的分支保护没得商量。 - 跳过 secret 扫描 hook——“我在 review 里抓得住”,总有你随手扫过去的那一次抓不住。