Prompt 注入检测器
detector 是一个从 prompt 到「置信度 + 类别」的函数。别的都只是凭感觉。
类型: Build 语言: Python 前置要求: 阶段 18 安全相关课程、阶段 19 Track A 第 25-29 课 预计时间: ~90 分钟
问题背景
团队在社交媒体上读到一个 jailbreak,写了一条正则,比如 r"ignore (all )?previous",上线,就管它叫 prompt injection 防御了。两周后同样的攻击换成 "disregard the prior" 打了进来,正则漏了,团队却怪到模型头上。这个 detector 从来没拿任何东西衡量过。没人知道 precision。没人知道 recall。没人知道它覆盖了哪些类别。这条正则就是个安全剧场的补丁。
一个诚实的 detector 是一个行为可度量的函数。给定一条 prompt,它返回一个落在 [0, 1] 区间的置信度,以及最匹配的类别。给定一份已标注的语料库,框架会让 detector 跑遍每个 fixture,按类别拆分成 true positive、false positive、true negative 和 false negative,并报告 precision 和 recall。团队读这些 precision 和 recall,决定要上线什么,决定下一个 sprint 把精力花在哪,然后不再瞎猜。
本 Capstone 构建一个分层的 detector:确定性子串规则、token 级正则,以及一个在规则跑之前先解码简单编码(base64、rot13、leet、零宽字符)的 normalize 环节。每一层都可以独立审计。每条规则都有一条针对某个类别的覆盖声明。runner 会产出一份按类别划分的混淆矩阵(confusion matrix),以及一份下游课程可以拿去画图的 CSV。
核心概念
这里的 detector 是一个 Rule 对象的列表。每条规则有一个 name、一个 category,以及一个函数 score(prompt) -> float in [0, 1]。规则要么触发,要么不触发。触发时,它的 score 就是它的置信度。聚合器把每条规则的 score 收拢成一个 Verdict,带 category(得分最高的类别)和 confidence(该类别里的最高分)。没有任何规则触发的 prompt 得 0.0,标为 benign。
三层,按顺序应用:
Normalize。 剥掉零宽字符和 bidi 控制符。把一份工作副本转小写。解码看起来像 base64、rot13、hex 的 token。把 leet-speak 数字换成对应的字母。保留原始 prompt 和 normalize 后的副本一起,因为有些规则想看到原始字节(零宽字符插入本身就是一个信号)。
子串规则。 手写的模式,比如
"ignore previous"、"as an unrestricted"、"answer starting with"、"sure, here is"。每个模式带一个类别和一个基础 score。规则在原始文本或 normalize 后的文本上任一命中即触发。正则规则。 token 级的模式,用来抓一整个家族。
r"\bignor\w*\s+(all|prior|previous|earlier)\b"覆盖一族 override。r"\b(decode|rot13|base64|hex)\b.*\banswer\b"抓 encoding trick。每条正则带一个类别和一个基础 score。
flowchart LR
P[prompt] --> N[normalize]
N --> S[子串规则]
N --> R[正则规则]
S --> A[聚合]
R --> A
A --> V[verdict:类别 + 置信度]
V --> M[指标 runner]
M --> CM[按类别的混淆矩阵]指标 runner 拿第 82 课的 taxonomy 产物,让 detector 跑遍每个 fixture,计算每个类别的 precision 和 recall。一条 prompt 的类别标签就是 fixture 的类别;detector 预测的类别就是 verdict 的类别。类别 C 的 true positive 是 fixture-category=C 且 verdict-category=C。false positive 是 fixture-category!=C 且 verdict-category=C。false negative 是 fixture-category=C 且 verdict-category!=C(或 benign)。runner 还接受一份 benign-prompt 列表,这样在安全文本上的 false positive 也能被度量到。
detector 不是安全防护本身。它只是安全防护会去组合的众多信号之一。按设计,它在 encoding-trick 和 instruction-override 上偏向 recall,并接受在 role-play 上只有中等 precision,因为 role-play 攻击会和正当的创意写作请求混在一起,而对于这些边界情况,安全防护会动用其他信号(规则引擎、classifier)。
injection-gate动手构建
语料库加载器读取第 82 课的 outputs/taxonomy.json。规则放在 code/rules.py 里,是数据而非代码。每条规则是一个字典,含 name、category、score,以及 substring 或 regex 之一。detector 类只编译它们一次。
normalize 环节用标准库的 re.sub 和 codecs。base64 normalize 会尝试解码任何 16 字符以上、看起来像 base64 的 token;成功就把该 token 替换成解码后的 UTF-8。rot13 normalize 用 codecs.encode(text, 'rot_13') 造一个候选,只有当候选里像词典词的单词比输入多时才保留它(在一个内置小词表上的廉价启发式)。
指标 runner 产出一份 JSON 报告,含每个类别的 precision、recall、F1,以及原始计数。detector 对某些 fixture 是故意答错的(尤其是那些看起来 benign 的 role-play prompt);报告把这点暴露出来,而不是藏起来。
实际使用
运行 python3 main.py。demo 加载 taxonomy,让 detector 跑遍每个 fixture,再让它跑一遍烘焙进 benign.py 的 benign-prompt 语料库,并打印每个类别的指标。outputs/detector_report.json 这个文件就是第 87 课安全防护要消费的产物。
拿去用
outputs/skill-prompt-injection-detector.md 记录了规则格式以及如何新增一条规则。
练习
- 为 context-smuggling(指令藏在 tool 结果的 JSON 里)新增一族规则。度量 recall 的提升,以及在 benign prompt 上的 false-positive 代价。
- 计算每条规则的贡献:对每条规则,统计如果删掉它会丢多少 true positive。按边际贡献给规则排序。
- 加一个
confidence_threshold旋钮。把它从 0 扫到 1,画出每个类别的 precision-recall 曲线。
关键术语
| 术语 | 通常用法 | 精确含义 |
|---|---|---|
| detector | 一个拦截攻击的模型 | 一个返回类别和置信度的函数,用 precision 和 recall 来评估 |
| normalize | 一个预处理步骤 | 一个把隐藏 token 暴露给后续规则的变换 |
| confusion matrix | 一张 2x2 表 | 按类别拆分的 TP、FP、TN、FN,用来算 precision 和 recall |
| precision | 整体准确度 | TP / (TP + FP),触发中判对的比例 |
| recall | 整体覆盖度 | TP / (TP + FN),detector 抓到的攻击比例 |
延伸阅读
本 Track 的第 84 到 87 课。这里的 detector 是端到端安全防护组合的三个信号之一。