防止 AI 改不该改的:能真正撑住的护栏

CLAUDE.md 里礼貌的规则拦不住 agent 动 .env 或生产配置。真正起作用的是这几层:deny 规则、ignore 文件、沙盒、hook、分支保护。

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阻止改动落进 mainGitHub 服务端不能——跑在服务端,不在本地

前两层阻止 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 *)"
    ]
  }
}

两个容易踩的点:

  • ReadEdit 的模式遵循 .gitignore 语义,不是从根目录开始的 glob。裸写的 Read(.env) 等价于 Read(**/.env),会匹配任意深度的 .env。前导一个 / 锚定到项目根(Edit(/infra/**)),而前导两个 // 是唯一能写出”文件系统绝对路径”的方式。
  • Read/Edit 的 deny 规则也覆盖 Claude Code 认识的那几个 Bash 文件命令——catheadtailsed——但覆盖任意脚本(一个自己 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 版本里弃用了 denylistCursor 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-writeon-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 月,标准方案是两层:

  1. gitleaks 挂成 pre-commit hook,让带 secret 的提交在本地就被拦住。用 pre-commit 框架装——加一个指向 github.com/gitleaks/gitleaks.pre-commit-config.yaml,跑 pre-commit install——或者直接 gitleaks protect --install。两种方式都会在写入前扫描每一次 commit。
  2. 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 跑 sudodanger-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 里抓得住”,总有你随手扫过去的那一次抓不住。

相关阅读

标签: #AI 编程 #教程