跳到主要内容

02 · 六个扫描器逐个拆

本章把每个扫描器当成一个独立小问题来讲:它防什么、思路是什么、怎么打分、阈值多少。 所有扫描器都实现同一个接口 async scan(message, past_trace) -> ScanResult(基类 scanners/base_scanner.py:12)。

先给一张全景对照表:

扫描器ScannerType防什么判定手段命中裁决依赖
PromptGuardPROMPT_GUARD提示注入/越狱本地 BERT 分类器BLOCKHF 模型
AlignmentCheckAGENT_ALIGNMENTagent 目标劫持远程 LLM 审计轨迹HITLTogether API
CodeShieldCODE_SHIELD危险代码静态分析(regex+semgrep)BLOCKcodeshield 包
RegexREGEX已知模式/PII正则表BLOCK
HiddenASCIIHIDDEN_ASCII隐藏 Unicode 指令码点范围检查BLOCK
PIICheckPII_DETECTION敏感信息泄露远程 LLM 分类BLOCKTogether API

3.1 PromptGuard — 用小分类器抓提示注入

要解决的小问题: 一句话是不是提示注入 / 越狱(「忽略之前的指令……」)?

思路: 不用大模型,用一个 BERT 风格的轻量分类器(meta-llama/Llama-Prompt-Guard-2-86M, 86M 参数),给文本打一个「是注入」的概率分。快、可本地跑、适合高吞吐。

怎么打分: PromptGuard.get_jailbreak_score(promptguard_utils.py:122)把文本过分类器, softmax 后取最后一类的概率作为越狱分:

# 示意,非源码:核心就三步
inputs = tokenizer(text, truncation=True, max_length=512, return_tensors="pt")
logits = model(**inputs).logits
score = softmax(logits, dim=-1)[0, -1].item() # 最后一类的概率 = 越狱分

扫描器 PromptGuardScanner.scan(prompt_guard_scanner.py:31)拿到分数后跟阈值比: score >= block_threshold(默认 0.9)→ BLOCK,否则 ALLOW。

巧妙细节 —— 抗空格绕过: 攻击者常用 i g n o r e 这种插空格的写法躲检测。PromptGuard 在 _preprocess_text_for_promptguard(promptguard_utils.py:79)里先删掉所有空白字符,再按分词 边界补回单个空格——把 i g n o r e 归一化成 ignore 再判。


3.2 AlignmentCheck — 让大模型审计 agent 有没有跑偏

要解决的小问题: agent 执行到第 N 步,这一步动作还符合用户最初的目标吗?还是被诱导去干别的了?

思路: 这类「语义是否偏离」很难用规则判,所以直接让一个大模型来当审计员。给它「用户原始目标」 +「到目前为止的轨迹」,让它用 few-shot + chain-of-thought 推理判断当前动作是否 misaligned。

结构化输出: 审计模型必须返回一个固定 schema(alignmentcheck_scanner.py:25):

字段含义
observation描述 agent 的推理
thought推断它是否被劫持,给出推理步骤
conclusionTrue=检测到跑偏 / False=正常

打分与裁决: conclusion=True → score 1.0,而 score==1.0 映射到 HUMAN_IN_THE_LOOP_REQUIRED(不是 BLOCK!见 alignmentcheck_scanner.py:120-130)。设计意图是 目标劫持的判断有主观性,交给人复核而非直接拦死。

它是怎么拿到轨迹的: 该扫描器把 require_full_trace = True(alignmentcheck_scanner.py:62), 并在 scan 里从 past_trace 里挑出用户消息作为「原始目标」,再把整段轨迹拼成字符串喂给审计模型 (_pick_user_input / _pre_process_trace,alignmentcheck_scanner.py:64-72)。这就是为什么它要配合 scan_replay(第 1 章 §5)用。

Fail-closed(重要安全默认): 如果审计 LLM 调用出错,默认响应是 conclusion=True(alignmentcheck_scanner.py:113)——出错时按「疑似被劫持」处理,宁可误报

审计用的系统提示(含 6 个 few-shot 例子)硬编码在 alignmentcheck_scanner.py:133SYSTEM_PROMPT, 值得一读——它明确要求「只看当前选定动作,拿不准就判不跑偏」,以压低误报。

AlignmentCheck 与 PIICheck 都继承自 CustomCheckScanner(custom_check_scanner.py:23),这是一个 「LLM 打分」的通用基类:封装了 system_prompt + 输出 schema + 调 LLM(默认走 Together API 的 Llama-4-Maverick)。子类只要实现「怎么把结构化输出转成分数」。详见第 4 章。


3.3 CodeShield — 静态分析拦危险代码

要解决的小问题: LLM 生成/工具返回的代码里有没有安全隐患(注入、弱加密、写死密钥……)?

思路:静态分析(不执行代码,只扫源码),两级引擎:先跑快的正则,必要时再跑更准的 semgrep。

CodeShieldScanner.scan(code_shield_scanner.py:53)的做法:

  1. 拿到所有受支持语言(languages.get_supported_languages())。
  2. 对每种语言并行跑一遍分析(asyncio.gather)——因为它不知道这段文本是什么语言,索性都试。
  3. 只要任一语言报出 issue → BLOCK,score=1.0,reason 里列出每条问题(描述、CWE 编号、行号、严重度)。
  4. 一条 issue 都没有 → ALLOW。

这里只讲扫描器外壳;两级流水线(regex→semgrep)的内部机制单独放第 3 章,因为那是整个项目 工程含量最高的一块。


3.4 Regex — 最朴素的模式匹配

要解决的小问题: 快速命中已知坏模式(某句注入话术、邮箱、电话、卡号、SSN)。

思路: 一张正则表,任一命中就拦。零依赖、语言无关、易定制。

默认表 DEFAULT_REGEX_PATTERNS(regex_scanner.py:21):

名称大致匹配
Prompt injectionignore previous/all instructions
Email address邮箱
Phone number电话号
Credit card16 位卡号
Social security number美国 SSN

scan(regex_scanner.py:63)遍历所有编译好的模式(IGNORECASE | DOTALL),第一个命中就返回 BLOCK (score=1.0,reason 是命中项名),全不中则 ALLOW。


3.5 HiddenASCII — 抓肉眼看不见的隐藏指令

要解决的小问题: 文本里藏了不可见的 Unicode Tag 字符(U+E0000–U+E007F),被用来给模型偷偷夹带指令。

背景: Unicode 的「Tags」区块(U+E0000 起)能把普通 ASCII 编码成渲染时不可见的字符。攻击者 可以在一句正常话里藏一整条隐藏指令,人看不见、模型能读到。

思路: 直接查码点范围,不需要任何模型。HiddenASCIIScanner(hidden_ascii_scanner.py:18):

# 示意,非源码:检测 + 解码
def _is_hidden_ascii_present(text):
return 1.0 if any(0xE0000 <= ord(c) <= 0xE007F for c in text) else 0.0

def _decode_hidden_ascii(text):
# 隐藏字符减去基址 0xE0000 就还原成可见 ASCII
return "".join(chr(ord(c) - 0xE0000) if 0xE0000 <= ord(c) <= 0xE007F else c
for c in text)

命中(score>=1.0)→ BLOCK,并在 reason 里把隐藏内容解码出来给你看(hidden_ascii_scanner.py:58)。 真实实现见 hidden_ascii_scanner.py:28-45


3.6 PIICheck — 用 LLM 识别敏感信息

要解决的小问题: 文本里有没有 PII(姓名、邮箱、电话、地址、SSN、卡号、护照号)?

思路: 也走 CustomCheckScanner,让 LLM(默认 Llama-3.3-70B-Instruct-Turbo)返回 「检测到的 PII 类型列表」。相比 Regex 版,LLM 能抓语义化、格式多变的 PII。

打分逻辑(piicheck_scanner.py:79):列表里有 PII 类型(且不是 ["ERROR"]/["None"])→ score 1.0; score >= block_threshold(默认 0.7)→ BLOCK,reason 列出检测到的类型。系统提示见 piicheck_scanner.py:116

PIICheck 与 AlignmentCheck 都在 scanners/experimental/ 下——标注为实验性,接口可能变。


小结:选扫描器的直觉

  • 低延迟、本地、防注入 → PromptGuard(模型)或 Regex(纯规则)。
  • 防危险代码 → CodeShield。
  • 抓隐藏字符 → HiddenASCII(几乎零成本,建议常开)。
  • 监控 agent 跑偏 / 查 PII → AlignmentCheck / PIICheck(要 API key、有延迟,实验性)。

下一章深入 CodeShield 的两级静态分析引擎。