AI Native 手册
护栏与安全

护栏体检:防止静默失效

护栏会被工具覆盖、被上游合并冲掉,所以要定期检查它们还在、还管用

试用中小工具产品组织阶段 · 通用更新于 2026-10-16

护栏失效时不会报错,只会悄悄消失。所以要像体检一样定期检查:会话开始时自动跑一个脚本,确认关键的 deny 规则还在、备份 hook 真的会备份;上游会改的共享文件,在 CI 里用行为断言守住;切换配置、大合并之后,再做一次全面体检。这套做法是我在 2026 年 10 月那次事故之后加上的,还在试,所以整页标 🧪。

为什么会失效:三条路径

亲历 下面三条,我是在同一次排查里一起发现的:

  1. 被工具覆盖。 用一个模型供应商切换工具从第三方模型切回官方时,它把 ~/.claude/settings.json 整份重写,权限规则、hook、插件启用状态全部丢失。表面症状只是“所有插件都关了”
  2. 被上游合并冲掉。 在一个 fork 的 Makefile 里加的备份步骤,在一次上游大合并中被覆盖
  3. 文件还在,没有挂载。 hook 脚本好好地躺在磁盘上,但 settings 里没有任何事件引用它

三条的共同点:没有报错、没有提示,你只会在真正需要它的那一刻,发现它已经不在了。还有一条连带的:hookify 这类插件生成的规则,靠插件本身的 hook 执行,插件被关掉,规则就一起失效。

原则:查行为,不查文本

grep 到 pg_dump 不代表备份会发生:它可能在注释里、在另一个 target 里,或者失败被 || true 吞掉了。settings 里有一条命令名带 db-backup 的 hook,也不代表它会匹配、会备份。所以下面的检查都是真跑一遍,看结果:喂一段模拟输入,换上假的 pg_dump 和 psql,看该发生的有没有发生、不该发生的有没有被拦住。

1. 会话开始自动检查

SessionStart hook 的标准输出会被加进 Claude 的上下文。利用这一点:检查脚本在一切正常时什么都不输出,发现问题就打印出来,Claude 会在开始干活之前提醒你。

保存为 ~/.claude/hooks/guardrail-check.sh,加上执行权限:

#!/usr/bin/env bash
# SessionStart hook:检查关键护栏还在不在、还管不管用。全部正常时什么都不输出。
# SessionStart 的标准输出会进入 Claude 的上下文,所以发现问题就打印出来,让 Claude 提醒你。
settings="$HOME/.claude/settings.json"
problems=()

# 1. 关键 deny 规则还在
for rule in 'Bash(rm -rf ~)' 'Bash(git push --force origin main *)' 'Bash(git reset --hard origin/main *)'; do
  jq -e --arg r "$rule" '(.permissions.deny // []) | index($r) != null' "$settings" > /dev/null 2>&1 \
    || problems+=("deny 规则不见了:$rule")
done

# 2. 备份 hook 挂着,而且真的会备份:取出 PreToolUse 上挂的命令,喂一段模拟输入。
#    假的 pg_dump 只记录自己被调用过,然后故意失败,所以不会产生真实备份
hook_cmd="$(jq -r '[.hooks.PreToolUse[]?.hooks[]?.command | strings | select(test("db-backup"))][0] // empty' "$settings" 2>/dev/null)"
if [ -z "$hook_cmd" ]; then
  problems+=("数据库备份 hook 没有挂载到 PreToolUse")
else
  shim="$(mktemp -d)"
  printf '#!/bin/sh\ntouch "%s/called"\nexit 1\n' "$shim" > "$shim/pg_dump"
  chmod +x "$shim/pg_dump"
  printf '%s' '{"cwd":"/tmp/guardrail-check","tool_input":{"command":"psql -c \"DROP TABLE t\""}}' \
    | PATH="$shim:$PATH" DATABASE_URL="postgresql://guardrail-check/fake" bash -c "$hook_cmd" > /dev/null 2>&1
  rc=$?
  [ -e "$shim/called" ] || problems+=("备份 hook 收到 DROP TABLE 却没有调用 pg_dump")
  [ "$rc" -eq 0 ] || problems+=("备份 hook 的退出码是 $rc,可能会拦住正常命令")
  rm -rf "$shim"
fi

# 3. hookify 还启用着(不用 hookify 就删掉这一项)
jq -e '.enabledPlugins["hookify@claude-plugins-official"] == true' "$settings" > /dev/null 2>&1 \
  || problems+=("hookify 插件没有启用,它的规则全部不生效")

if [ ${#problems[@]} -gt 0 ]; then
  echo "【护栏体检】发现问题,开始工作前请先告诉用户:"
  printf -- '- %s\n' "${problems[@]}"
fi
exit 0

第 2 项是行为检查:它把 settings 里真正挂着的那条命令跑一遍,喂给它一条 DROP TABLE,看它有没有去调用 pg_dump、退出码是不是 0。脚本被删、正则写坏、hook 会误拦正常命令,都会在这里暴露出来。按你自己的情况,改 deny 列表和插件名;不用 hookify 的话,删掉第 3 项。

关键一步:体检 hook 不要和被检查的东西放在同一个文件里。 如果它挂在 ~/.claude/settings.json,切换工具整份重写这个文件时,体检会和规则一起消失。把它挂在托管设置里(工具改不到),或者挂在各项目的 .claude/settings.json 里(进了 git,被改动能从 diff 里看出来):

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          { "type": "command", "command": "\"$HOME\"/.claude/hooks/guardrail-check.sh" }
        ]
      }
    ]
  }
}

托管设置的路径和写法见权限规则。

2. 在 CI 里做行为断言

fork 里你改过、上游也会改的文件(Makefile、部署脚本、CI 配置),在 CI 里加一步行为断言。以三层护栏里的 db-reset 为例:用假的 pg_dump 控制备份成败,用假的 psql 记录 reset 有没有真的执行。保存为 scripts/check-guardrails.sh:

#!/usr/bin/env bash
# CI 行为断言:备份成功时 db-reset 照常执行;备份失败时必须停下,不能继续 reset。
# 用假的 pg_dump 控制备份成败,用假的 psql 记录 reset 有没有真的执行。
set -uo pipefail
shim="$(mktemp -d)"
log="$shim/psql-calls.log"
printf '#!/bin/sh\necho "psql $*" >> "%s"\n' "$log" > "$shim/psql"
chmod +x "$shim/psql"

run_reset() {  # $1:假 pg_dump 的退出码
  printf '#!/bin/sh\nexit %s\n' "$1" > "$shim/pg_dump"
  chmod +x "$shim/pg_dump"
  : > "$log"
  PATH="$shim:$PATH" HOME="$shim" DATABASE_URL="postgresql://ci/fake" make db-reset > /dev/null 2>&1
}

run_reset 0 && [ -s "$log" ] \
  || { echo "::error::对照组失败:备份成功时 db-reset 没有走到 reset,目标可能被删了"; exit 1; }
if run_reset 1 || [ -s "$log" ]; then
  echo "::error::备份失败时 db-reset 没有停下"; exit 1
fi
echo "护栏断言通过:备份失败时 db-reset 会停下"

脚本里有两组:对照组确认 db-reset 目标还在、能走到 reset,否则“备份失败时没执行”可能只是因为目标被删了;实验组确认备份失败时 reset 一步都没走。你的 reset 实际调用的如果不是 psql(比如 supabase、prisma、docker),把假命令换成它。

然后在 GitHub Actions 里加一步,顺带确认 ask 规则还在:

- name: 护栏断言
  run: |
    bash scripts/check-guardrails.sh
    jq -e '.permissions.ask | index("Bash(make db-reset *)") != null' .claude/settings.json > /dev/null \
      || { echo "::error::.claude/settings.json 里的 ask 规则不见了"; exit 1; }

下一次上游合并冲掉你的改动时,PR 会直接变红,而不是半年后才被发现。复盘里写下的检查命令,都应该这样变成 CI。

3. 全面体检:/ainative:doctor

本手册的插件带了一个只读的体检命令 /ainative:doctor:检查插件和 skill 是否冗余或互相抢触发、权限规则和 hook 是否还在、配置有没有被切换工具覆盖。适合在这些时候跑:

  • 换电脑或重装之后
  • 切换模型供应商之后
  • 上游大合并之后
  • 每个月一次

安装和其他功能见本手册的 Claude Code 插件。

不装任何东西,也能看一眼

命令看什么
/permissions所有权限规则,以及每条来自哪个文件
/hooks按事件列出所有已挂载的 hook
/statusSetting sources 一行:托管设置有没有生效
claude doctor安装是否健康、哪些设置因为写错被丢弃
claude plugin list插件是否还处于启用状态

这几条只能告诉你“配置还在”,不能告诉你“还管用”。管不管用,要靠上面两种行为检查。

常见坑

相关

本页目录