CLI Agent 高频问题排查
装包失败 / 权限报错 / API 限流 / context 超限 / hook 死循环等终端 Agent 高频问题的快速定位与处置
高危权限治理:dangerously-skip-permissions / 容器隔离 / CI 场景 / 团队规范的成体系做法,含真实事故案例
内容摘要
CLI Agent 跑在终端里,比 IDE 插件、网页版多了一项致命的能力——**它可以直接拿到 shell**。Claude Code 跑 `Bash`、Codex CLI 跑 `shell`、Gemini CLI 跑 `run_shell_command`,本质上都是把模型生成的命令塞进你电脑的进程树里执行。一旦你为了"少敲几下回车"而把权限放开到底,Agent 就有能力 `rm -rf` 你的家目录、把 `.env` 里的 token 发到外网、把 SSH 私钥写进一段它觉得"很合理"的 commit。这就是 **"危险权限"** 这个词在 CLI Agent 语境下真正的含义。
CLI Agent 跑在终端里,比 IDE 插件、网页版多了一项致命的能力——它可以直接拿到 shell。Claude Code 跑 Bash、Codex CLI 跑 shell、Gemini CLI 跑 run_shell_command,本质上都是把模型生成的命令塞进你电脑的进程树里执行。一旦你为了"少敲几下回车"而把权限放开到底,Agent 就有能力 rm -rf 你的家目录、把 .env 里的 token 发到外网、把 SSH 私钥写进一段它觉得"很合理"的 commit。这就是 "危险权限" 这个词在 CLI Agent 语境下真正的含义。
具体来说,"危险权限"通常指三类被默认询问、可以一键绕过的能力:
Bash / shell 工具本身Write / Edit 对文件系统的修改,尤其是 repo 之外的路径WebFetch / curl / wget 把数据传到外部域名CLI Agent 默认会在执行这些动作前弹窗征求人类同意,这是为了挡住两条最高频的攻击路径:模型自己的判断失误("我以为这个文件没用")和 prompt injection(一段 README 里的恶意指令骗 Agent 把 ~/.aws/credentials 上传出去)。把权限"绕过"的代价,就是把这两条防线一起拆掉。
绕过的代价不是抽象的。2025 年 10 月,开发者 Mike Wolak 跑 Claude Code 时开了 --dangerously-skip-permissions,Agent 在一次"清理临时文件"中执行了从 / 开始的 rm -rf,日志里几千行 Permission denied: /bin /boot /etc,最终把这台机器上所有用户可写文件全部清空。还有一类更隐蔽的事故:2026 年 Johns Hopkins 的研究者用一段 prompt injection 同时劫持了 Claude Code Security Review、Gemini CLI Action、GitHub Copilot 三个跑在 GitHub Actions 里的 Agent,让它们把仓库 secret 发给了攻击者控制的服务。这两类事故的共同点都是"权限太大 + 沙箱太薄"。
危险权限治理不是一个开关,而是一组叠加的层。下图是当前业界(参考 Anthropic 官方 devcontainer、trailofbits 的 sandbox 模板、NVIDIA 的 Agentic Workflow 安全指南)收敛出来的纵深防御模型:
每一层都不是万能的。L4 hooks 拦不住"hooks 配置文件本身被改";L2 容器拦不住"挂载进来的 .env 被读出去再 POST 出去";L1 隔离拦不住"开发者自己把生产 token 拷到沙箱机器上"。真正的安全来自这些层之间的冗余——某一层失守时,下一层还兜得住。
| 要素 | 作用 | 失守会怎样 |
|---|---|---|
| permission mode | Claude Code 的权限模式:default / acceptEdits / plan / bypassPermissions | 错开 bypassPermissions 会把所有提示拆掉 |
| allowedTools / deniedTools | 在 settings.json 里硬白/黑名单 | 名单不写或写错时 hook 是最后一道闸 |
| hooks | PreToolUse / PostToolUse 等生命周期钩子 | 可以拦住"绕过权限提示后的具体命令" |
| container/sandbox | devcontainer / Docker / Firejail 的进程与文件隔离 | 拦不住挂载进容器的 secret 被外传 |
| network egress 控制 | iptables / 防火墙白名单 | 拦不住通过白名单域名(如 GitHub)的隐写攻击 |
下面四段是最常用的配置实操,按 "团队 settings → 容器隔离 → CI 场景 → hook 拦截" 的顺序逐层加固。
把 settings.json 提交进仓库(路径 .claude/settings.json),让每个团队成员一启动 Claude Code 就拿到一致的安全基线。这一份模板覆盖了 95% 团队的实际需要:
{
"permissions": {
"defaultMode": "default",
"allow": [
"Read",
"Edit(./src/**)",
"Edit(./tests/**)",
"Bash(npm run test:*)",
"Bash(npm run lint:*)",
"Bash(git status)",
"Bash(git diff:*)",
"Bash(git log:*)"
],
"deny": [
"Bash(rm -rf:*)",
"Bash(sudo:*)",
"Bash(curl:*)",
"Bash(wget:*)",
"Bash(chmod 777:*)",
"Bash(git push --force:*)",
"Edit(./.env*)",
"Edit(./**/credentials*)",
"Edit(/etc/**)",
"Edit(~/.ssh/**)",
"WebFetch"
]
},
"hooks": {
"PreToolUse": [
{ "matcher": "Bash", "hooks": [{ "type": "command", "command": ".claude/hooks/bash-guard.sh" }] }
]
}
}
要点:
defaultMode 不要写成 bypassPermissions,团队基线必须是"默认要问"。allow 用细粒度的工具+参数模式,比起整段 Bash 放开靠谱得多。deny 列表要把"破坏性 + 提权 + 外传"三类常见动作堵死,注意 :* 是 Claude Code 的通配语法。Anthropic 官方推荐的隔离方式是 Development Containers,可以一行 feature 引入。下面这份 .devcontainer/devcontainer.json 是 Anthropic 官方模板的精简版,重点在 firewall 白名单 + 非 root 用户:
{
"name": "claude-code-sandbox",
"image": "mcr.microsoft.com/devcontainers/typescript-node:20",
"features": {
"ghcr.io/anthropics/devcontainer-features/claude-code:1": {}
},
"remoteUser": "node",
"mounts": [
"source=${localWorkspaceFolder},target=/workspace,type=bind"
],
"runArgs": [
"--cap-add=NET_ADMIN",
"--cap-add=NET_RAW"
],
"postCreateCommand": "sudo /usr/local/bin/init-firewall.sh",
"containerEnv": {
"ANTHROPIC_API_KEY": "${localEnv:ANTHROPIC_API_KEY}"
}
}
init-firewall.sh 默认拒绝所有出站,再放行 npm registry、GitHub API、Anthropic API 等少数白名单域名。这样即便 Agent 在容器里被 prompt injection 控制,也无法把数据 POST 到攻击者的服务器。容器跑 --dangerously-skip-permissions 的代价被显著降低——但请记住官方明确写过的一句话:容器不能阻止 Agent 把容器内能看到的 Claude Code 凭证、.env 文件外传。所以即便在容器里,secret 也不该裸挂进去。
GitHub Codespaces 直接吃这份 devcontainer 配置;Cursor、VS Code 也都内置 devcontainer 支持,团队基线只用维护一份。
CI 是危险权限的高发地:因为 CI 没有人盯着,开发者倾向于直接开 --dangerously-skip-permissions。下面是一份"在 PR 上跑 Claude Code 自动 review"的最小安全模板:
name: claude-review
on:
pull_request:
types: [opened, synchronize]
permissions:
contents: read
pull-requests: write
jobs:
review:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: run claude code in sandboxed mode
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
# 不开 dangerously-skip-permissions,依赖 settings.json 里的 allow 名单
npx -y @anthropic-ai/claude-code \
--settings .claude/ci-settings.json \
--print "review the diff and post review comments" \
--output-format stream-json
关键安全点:
permissions: 用最小化的 GITHUB_TOKEN scope,不要默认 write-all。persist-credentials: false 防止 GITHUB_TOKEN 被写进 .git/config 后被 Agent 读到。timeout-minutes 兜底防 Agent 死循环跑爆 minute。--dangerously-skip-permissions:CI 的"无人值守"恰恰是最不该绕过权限的场景。settings.json 的 deny 名单是字符串匹配,覆盖不了"bash -c 'r''m -rf /'"这种变形。真正可靠的兜底是 hook 脚本——它在 Agent 想跑命令、命令真的下到 shell 之前那一瞬间执行,可以做完整的 AST/正则判断。下面是一份 .claude/hooks/bash-guard.sh 示例:
#!/usr/bin/env bash
# PreToolUse hook:在 Bash 工具真正执行前拦截高危命令。
# Claude Code 会把工具调用的 JSON 通过 stdin 传进来。
set -euo pipefail
input="$(cat)"
cmd="$(printf '%s' "$input" | jq -r '.tool_input.command // empty')"
# 高危命令模式(按需扩充,建议团队共维一份)
deny_patterns=(
'rm[[:space:]]+(-[a-zA-Z]*r[a-zA-Z]*[[:space:]]|--recursive)'
'sudo[[:space:]]'
'chmod[[:space:]]+777'
'curl[[:space:]].*\|[[:space:]]*(sh|bash|zsh)'
'wget[[:space:]].*\|[[:space:]]*(sh|bash|zsh)'
'git[[:space:]]+push[[:space:]].*--force'
':\(\)\{[[:space:]]*:\|:&[[:space:]]*\};:' # fork bomb
'/dev/(sda|nvme|tcp)'
'\.ssh/(id_|authorized_)'
'\.aws/credentials'
)
for pat in "${deny_patterns[@]}"; do
if [[ "$cmd" =~ $pat ]]; then
# exit 2 是 Claude Code 约定的"阻断"信号:阻止此次 tool 调用,并把 stderr 反馈给模型
echo "blocked by bash-guard: matches /$pat/" >&2
exit 2
fi
done
exit 0
退出码语义来自 Claude Code hooks reference:exit 2 是阻断信号,Agent 会拿到 stderr 内容并知道"刚才那条命令被拦了,请换一个思路"。这一层兜底的好处是模型再怎么被 prompt injection 也绕不过——hook 是 shell 进程,不是 prompt,无法被劝说。
四款主流 CLI Agent 在权限/沙箱设计上有显著差异,理解差异有助于团队按工具特性配规范。
| 工具 | 权限模型 | 默认沙箱 | "全权限"模式 | 进程内拦截能力 | 推荐用法 |
|---|---|---|---|---|---|
| Claude Code | 4 档:default / acceptEdits / plan / bypassPermissions | 无(依赖容器) | --dangerously-skip-permissions;2026-03 起官方推荐用 auto mode 替代 | hooks(PreToolUse/PostToolUse 等 8 类)+ allow/deny 名单 | 配 devcontainer + hooks 双层 |
| Codex CLI | 两轴:approval policy × sandbox mode | 有:默认 sandbox(macOS 用 Seatbelt,Linux 用 Landlock+seccomp) | --full-auto,但仍在 sandbox 内 | sandbox 内 syscall 限制 + exec_policy | 直接信任默认沙箱即可 |
| Gemini CLI | 4 档:default / auto-edit / yolo / sandbox | 无(可选 --sandbox) | --yolo,只能命令行传,不能写进 settings 默认 | sandbox 模式(Docker/Podman)+ trusted folders | 长期运行务必加 --sandbox |
核心区别:Codex CLI 的设计哲学是"沙箱在前,权限在后"——即便开了 --full-auto,还是被 OS 级 sandbox 兜底,所以实际事故率最低;Claude Code 走的是"协议在前,沙箱在外"——内置 hooks/permissions 协议非常完备,但 OS 级隔离要靠用户自己加 devcontainer;Gemini CLI 则在 yolo 模式上做了一个有意思的克制——yolo 不能写进 settings.json 的默认值,必须每次显式 --yolo,意在用"麻烦"对抗"惯性"。
| 误区 | 准确理解 |
|---|---|
以为 --dangerously-skip-permissions 在 CI 里安全("反正没人在键盘前看着,不绕过就走不动") | CI 才是最不该绕过的场景:无人值守 + 长时间持有 secret + 网络可达,恰好凑齐了攻击三要素。CI 应该用更细的 allowedTools 名单,而不是更松的开关 |
| 以为容器隔离能防 secret 外传 | 容器只隔离进程和文件系统。如果你把 ~/.aws、.env 挂进容器,Agent 在容器里照样能读到然后通过白名单域名(GitHub raw、issue 评论)外传。Anthropic 官方文档明确写过这一点 |
| 以为 Firejail 是 Linux 上的银弹 | Firejail 需要 setuid root,配置语言冷僻,whitelisting 粒度只到 /home、/tmp 这一级,且只沙箱 Agent 进程本身——Agent 调起的子进程(npm、make、gcc)经常需要打破沙箱才能跑通,结果是用户被频繁的拒绝提示训练成"全部允许"。Firejail 适合作为额外的一层,不是主防线 |
| 以为 settings.json 的 allow/deny 写得够细就够了 | allow/deny 是字符串匹配,对付不了 `bash -c '$(echo cm0gLXJmIC8K |
| 以为"只读权限不会出事" | 读权限就是 secret 外传的入口。Agent 能读到的 token、history、.git/config 里的 PAT、~/.gitconfig 里的邮箱,都可以经由 Agent 的合法网络请求被发出去——这是 prompt injection 类攻击最常见的路径 |
| 以为升级 Claude Code 自动获得新安全特性 | 历史上 Claude Code 0.x 多次默认行为变更(权限模式默认值、hook 字段重命名),团队 settings.json 不跟着升就会失效。Anthropic 2026-03-31 那次源码 sourcemap 泄露,也提醒团队不要把 Agent 当作不会出问题的黑盒,关注 release note、订阅安全公告 |
| 以为 hook 配置不会被改 | hook 配置写在 .claude/settings.json 里,Agent 本身有权 Edit 这个文件。必须把 settings.json 加进 deny 名单:Edit(.claude/**),否则一段 prompt injection 就能让 Agent 关掉自己的 guard |
| 优势(建立纵深防御后) | 劣势(即便建好了也要承认) |
|---|---|
| 明显降低事故面:rm -rf 类、git push --force 类、外传 secret 类的常见 hot path 被堵死 | 完全的 prompt injection 防御目前不存在:模型对内容的理解能力本身就是攻击面 |
| 团队基线一致:settings.json 进仓库后,新成员开箱即合规 | 维护成本高:deny 名单和 hook 模式需要随工具升级、攻击手法变化持续维护 |
| CI/CD 可审计:所有命令通过 hook 留痕,事后可追溯 | 会牺牲一定开发效率:高强度的拦截会出现"明明我想跑这条命令也被拦了"的体验损耗 |
| 多 Agent 通用:Claude Code 的 hook 思路、Codex 的 sandbox 思路可以混用 | 隔离粒度有上限:OS 级沙箱拦不住"挂载进容器的 secret 被合法读取再合法外传" |
| 官方支持持续加强:Anthropic 在 2026-03 推出 auto mode 用分类器替代 dangerous bypass,趋势利好 | 生态不一致:四款主流 CLI Agent 的权限模型差异大,团队规范需要按工具一一适配 |
参考答案:
危险权限治理的本质是"在出事前有人或机制能阻断"。CI 同时拥有三件事:(1) 没有人在键盘前,模型决定要执行就会执行;(2) 长时间持有高权限 token,包括仓库写权限、云服务凭证、包管理 publish token;(3) 完全的网络可达性,可以向任意外网发请求。
把权限提示一关,等于把"模型在错误判断"和"prompt injection 在仓库 README 里植入恶意指令"这两条攻击路径全部失防。Johns Hopkins 2026 年那次研究就是用一段 issue 评论,让跑在 GitHub Actions 里的 Claude Code Security Review、Gemini CLI Action、Copilot 同时把仓库 secret POST 给了攻击者。
正确做法是反过来:CI 是 allowedTools 应该写得最严的场景,而不是最松。CI 跑的任务是固定的,能用细名单写死就一定要写死。
参考答案:
OS 级沙箱(Firejail / Landlock / seccomp)和容器级隔离(Docker / devcontainer)都是在进程边界上做隔离——它们能保证 Agent 看不到沙箱外的文件、调不到沙箱外的命令。但是 secret 外传往往不是"突破沙箱",而是"在沙箱里把合法读到的东西通过合法的网络出口发出去"。
举两个具体场景:(1) 你把 ~/.aws/credentials 挂进 devcontainer 是为了 Agent 能调 AWS API,但同样的内容也可以被读出后塞进一个 gh issue comment;(2) 容器的 firewall 白名单放行了 api.github.com,但攻击者完全可以让 Agent 把 secret 编码到一个 GitHub gist 的内容里 POST 出去——出口是合法的,内容是 secret 的。
什么情况下需要再加一层?三种:(a) secret 不放进沙箱,改用临时 token、按需注入、用完即销,从源头消除可读性;(b) 加 PostToolUse hook 做出站审计,对所有 WebFetch/curl/git push 做 payload 哈希记录,事后能追溯;(c) 物理/账号隔离,Agent 运行在独立云账号、独立 GitHub Org,与生产 secret 域物理无交集,即便外传也只能拿到玩具 token。
记住一句话:沙箱降低攻击面,凭证管理才决定爆炸半径。
参考答案:
hook 比 settings.json 的 allow/deny 强,是因为它拿到的是展开后的命令、能跑任意脚本判断;但它仍有几个根本短板。
短板一:自我修改攻击。 hook 配置写在 .claude/settings.json 里,而 Agent 有权 Edit 这个文件。一段 prompt injection 完全可以是"先把 .claude/settings.json 里的 hooks 字段删掉,再 rm -rf"。对冲方式有三层:(a) settings.json 加进文件级 deny;(b) 把关键 hook 移到仓库外、Git 不可达的位置(如 ~/.claude/global-hooks/);(c) PostToolUse 监控 settings.json 是否被改,触发即告警并 kill session。
短板二:模式匹配不可能枚举完所有变形。 rm -rf 可以写成 \rm -rf、bash -c 'r''m -rf'、echo cm0gLXJmIC8 | base64 -d | bash、调用一个看起来无害的 npm 包内部跑 rm。对冲方式不是堆模式,而是改变防御层级——把 rm 这个二进制本身在沙箱里换成一个被包装过的 wrapper,或干脆用只读文件系统挂载(read-only bind mount)让 rm 在物理上无效。
短板三:hook 只看命令,不看意图。 一条 git push origin main 在测试仓库无害,在生产仓库灾难。要做到"按上下文判断",需要把 hook 升级成调用 LLM 二次审核(Anthropic 2026-03 发布的 auto mode 做的就是这件事——一个独立的 Sonnet 4.6 分类器评估"作用域升级 / 不受信基础设施 / prompt injection"三类风险)。但这又引入新成本:审核延迟、二次模型也可能被 prompt injection。
结论: 安全是不断把单点防御变成纵深防御的过程。hook 是必要的一层,但它需要和容器、网络白名单、凭证最小化、事后审计、独立环境一起组合,才能把剩余风险压到可接受的水位。承认"没有终极方案"本身,就是这道题最重要的答案。
优先展示同分类且标签更接近的内容,方便继续串联学习。
装包失败 / 权限报错 / API 限流 / context 超限 / hook 死循环等终端 Agent 高频问题的快速定位与处置
把 prompt cache 用满、把上下文预算分配清楚的实战策略,结合 Claude Code 实际数据讲清"省钱"和"省时间"两个目标