同样的字节,不同的权威:Chat-Template 提示注入中保留 token 表示的作用

  • 关联论文:2609.35932
  • 作者:flyP
  • 更新:2026-10-01

一、一句话结论

prompt injection 之所以对 LLM agent 威力巨大,根本原因不是「文本像不像系统指令」,而是聊天模板里那几个 reserved token(如 <|im_start|>)背后的单向量学到了「我是控制符」;把它们用普通 subword 拼出来、文本一模一样,攻击成功率在三个开源模型族上会掉 39–66 个百分点。

二、这篇论文在解决什么真问题

LLM agent 越来越多地把不可信内容(网页、邮件、tool 返回值、RAG 检索片段)塞进上下文。攻击者只要在那些内容里塞一句"忽略之前的指令……",配上聊天模板的 marker(<|im_start|>system\n 之类),就能劫持 agent 去执行恶意动作。

业界已有大量防御研究(分隔符、提示加固、policy sandwich、CaMeL 等),但它们默认存在一个隐含假设:tokenizer 是中立的,攻击者和防御者面对的是同一段字节。本文打破这个假设,指出:字节相同不代表 token 相同,更不代表模型「看到」的内容相同。tokenization runs on the server,防御者(运行 tokenizer 的一方)替攻击者决定了 token 形态,于是攻击成功率成了一个可被测量、被攻击、被防御的变量。

论文作者是北大 Yan Zhan(PKU),GitHub Byte-Authority、HF 数据集 YanZhanPKU/Byte-Authority-Evaluation,9-28 入库。

三、核心方法:把「字节」剥成「token 表示」

3.1 关键观察

对一个聊天模板 marker,比如 <|im_start|>: - 路径 A(reserved):分词器命中 special token 表,输出单个 id,对应模型 embedding 表里一个学过的向量。 - 路径 B(subword):把它当作普通字符串,拆成若干 BPE/SentencePiece 子词(比如 <、|im、_start、|>),对应若干普通词向量的拼接。

两条路径解码出的可见文本完全一致(decode 之后字节级相同),但进入模型的 token 序列不同。于是可以用它做一次干净的因果隔离实验:文本不变,只换 token 表示。

3.2 实验设计

伪代码:

marker_text = "<|im_start|>system\nYou are a helpful assistant..."

# 路径 A:reserved
ids_a = tokenizer.encode(marker_text, add_special_tokens=False)
#   → [special_id_im_start, ...] 单 token 进 embedding

# 路径 B:subword(关键:禁用 add_special_tokens,并对该 marker 强制走普通词表)
ids_b = tokenizer.encode(marker_text, add_special_tokens=False,
                          allowed_special=set())
#   → 一串普通 BPE id

# 控制组 B':插入等量无关 filler subword,控制「多几个 token」本身的影响
filler = pick_neutral_subwords(n=len(ids_b) - len(ids_a))
ids_b_ctrl = ids_a + filler

三组喂给同一个模型同一段 payload,只对比攻击成功率差值。

3.3 多通道验证

  • InjecAgent benchmark:四个开源模型族(Llama-3.1、Qwen3、DeepSeek 等)。
  • AgentDojo:多轮 agent 任务,验证单轮结论能否迁移到工具调用链。
  • 向量级探针:用 marker 的 subword 向量做平均 → 不能复现权威;用最近邻普通 token 的向量 → 在 Llama-3.1 上恢复攻击。
  • 自适应攻击:攻击者主动搜「非 reserved、但 embedding 最近的 token」作为替代 marker。
  • 部署侧统计:扫了 HF 上下载量 Top 400 的 chat 模型、67 套 tokenizer 配置,看 encode_special 默认行为是否覆盖 tool protocol token。

四、关键实验与数据

模型族 reserved→subword 攻击成功率下降 AgentDojo 迁移 备注
三个开源族(Llama 类等) -39 ~ -66 pp 同样显著 权威主要在 reserved 向量
Qwen3-8B -8 pp 同趋势 关闭 reasoning block 后差距扩大到 50 pp
Llama-3.1 用最近邻 token 攻击恢复 — 证明权威「可由一个向量携带」
自适应攻击 三/四族找到 embedding 邻居 — 防御不是免费的
67 套 tokenizer 配置 / Top 400 模型 33 套(覆盖 255 个模型)的 tool token 不在 special 名单里 — 默认 mitigation 失效
  • 每一组 base → instruction-tuned 对比里,instruction tuning 都加强了模型对 reserved marker 的偏好,所以 chat 模型反而比 base 更脆弱。
  • 标准 mitigation:tokenizer.encode(..., add_special_tokens=False, allowed_special=set()) 只对配置里声明 special 的 token 生效,对 agent tool protocol 的专用 token(如 <|tool_call|>)默认不动,这条通道依然可注入。

五、亮点

  1. 控制变量做得极干净。很多 prompt injection 研究把"文本里多了几个 token"和"token 含义变了"混在一起,作者用 filler subword 控制了"多几个 token"这个混淆项,差值才有因果意义。
  2. 抽象层级选得巧。不去测哪一个 prompt 最有效,而是问「权威住在 token 表示的哪一层」——这是更底层的诊断。
  3. 向量级证据闭环。subword 均值不能复现、最近邻可以复现,直接把"权威是一个向量"这个直觉钉死。
  4. 部署侧统计给出真实威胁面。33/67 tokenizer 配置、255/400 模型这个数字让"理论漏洞"变成了"可触摸的存量风险"。

六、局限与诚实标注

  • 小样本实验台:开源模型族覆盖到 Llama、Qwen、DeepSeek 等,但闭源(GPT、Claude、Gemini)完全无法复现——分词器和 embedding 都不公开。
  • Qwen3 的反例需要 reasoning 控制才能暴露:如果不做 reasoning-block 抑制实验,读者会以为 Qwen3 不受影响,这是单点结论误读的风险。
  • 自适应攻击只做了"找最近邻":真正的攻击者可能用 gradient、白盒 jailbreak、甚至 fine-tune 自己的 marker;论文没有覆盖完整攻击面。
  • 基准局限:InjecAgent 和 AgentDojo 都是英文、偏 tool-call 场景;中文、图像多模态、长上下文场景原文未明确覆盖。
  • 没有给出端到端生产防御方案:诊断出了 reserved vector 是元凶,但如何在大模型 + 大流量服务里普适地"消解 reserved 权威",论文没给出工程答案(这个我读完后认为作者自己也意识到)。

七、对工程落地的启发

  1. 凡是把不可信内容塞进上下文的 agent,都应当在拼 prompt 之前主动把不可信文本当作"非 special"段过一遍 tokenizer,并检查工具相关 special token(<|tool_call|>、<|tool_result|>、<|endoftext|> 等)是否在你的 allowed_special 集合里。默认配置大概率不安全。
  2. 不要相信「分隔符够明显就行」。攻击者和模型学到的,是 token 表示的语义,不是字符的语义;任何防御都得落到 tokenizer 这一层。
  3. 评估 prompt injection 的实验必须分两组:reserved token 路径 vs 强制 subword 路径,否则你测的可能只是"模型对某种字符串样式的敏感度",而不是"真正的注入抵抗力"。
  4. red-team 应该把"找 reserved token 的 embedding 邻居"作为基本动作——这篇论文已经证明,3/4 模型族都找得到。
  5. 监控维度:HF 上一旦上新模型,tokenizer_config.special_tokens_map 与 added_tokens_decoder 要纳入 SBOM 类静态扫描项。

八、工程落地的具体坑(≥5 条)

# 坑 现象 影响 修复
1 tokenizer.encode(text) 默认会吞掉 special token 用户代码里看似安全的字符串,在 tokenizer 输出里变成了一个 reserved id,绕过了用户的所有字符串过滤 prompt injection 检测失效、regex 防御被旁路 调用前显式 allowed_special=set(),并对 tool-output 段强制不走默认 special
2 chat template 的 marker(如 <|im_start|>)在多模型混用时漂移 Llama 用 <\|begin_of_text\|>、Qwen 用 <|im_start|>、DeepSeek 又不一样 攻击者只要换一个模板前缀就能跨模型复用 payload 防御侧必须按模型族分别做 payload 规范化,不能用统一的"已知 marker 黑名单"
3 instruction-tuned 模型比 base 模型更信任 reserved marker 以为"对齐"会让模型更难被骗,实际相反 越对齐、越依赖 reserved token 的权威,攻击越容易 把"对 reserved token 的怀疑度"纳入 RLHF / SFT 评估指标
4 agent framework(LangChain / AutoGen / 自研)的 tool 调用层默认走完整 chat template tool 返回值被包进 <|tool_result|>... 段,攻击者只要在返回值里塞 </s><|im_start|>system 就能伪造系统消息 整条 agent 链被劫持,且这种攻击完全发生在服务端,client 看不到 tool-output → model 之间增加"reserved token 隔离层",并把 marker 替换为不可信文本之外的随机 nonce
5 自适应攻击者会找 reserved token 的 embedding 最近邻 静态黑名单挡不住攻击者用一个没在黑名单但语义等价的 token 33/67 tokenizer、255/400 模型的统计意味着存量风险巨大 red-team 工具里加入"embedding 邻居搜索"作为基本攻击面
6 add_special_tokens=False 不等于"禁掉所有 special" 开发者以为已经安全,实际只有 allowed_special 集合里的会被保留;某些版本还会对 additional_special_tokens 做隐式合并 看似加固、实际还是被注入 显式 allowed_special=set() 并用单元测试断言"特殊字符串 → 普通 subword 序列"
7 reasoning 模型需要额外关掉 CoT 才能暴露漏洞 Qwen3-8B 不抑制 reasoning 时只掉 8 pp、抑制后掉 50 pp 用 reasoning 模型做安全评估会严重低估风险 评估 prompt injection 时强制 do_sample=False + max_thinking_tokens=0,否则对比不可信

九、与同方向工作的关系

  • Prompt injection 攻防:与 Perez & Ribeiro、Goodside、Willison 的早期工作同一脉络,但本文把"为什么有效"从 prompt engineering 层下沉到了 representation 层。
  • Reserved token / special token 研究:与 Geva et al. 的"knowledge neurons"、Elhage et al. 的"induction heads" 思路相通——单向量承载高层功能。
  • Agent 安全框架(CaMeL、Microsoft Spotlighting、Meta's LlamaFirewall):本文给出的结论对它们的有效性提出了新约束——如果底层 tokenizer 没处理好,上层 policy 框架可能成为摆设。
  • Tokenizer 侧攻击:与既往 BPE/SentencePiece adversarial token("SolidGoldMagikarp" 之类)形成呼应,本文把"看似无害的 special token" 也纳入攻击面。

十、适合谁读

  • Agent / 应用 LLM 工程师:直接影响生产安全设计。
  • 红队 / 安全研究员:新攻击面 + 新评估协议。
  • Tokenizer 与模型架构研究者:单向量"权威"是个干净的实验对象。
  • 值得一读的"非典型"读者:做 RLHF / 对齐的研究者——本文给了一个反直觉的实证:对齐可能放大而非抑制对控制符的依赖。

十一、一句话回顾

把安全问题的边界从"prompt 文本"挪到"token 表示",是从工程补丁转向机理诊断的一次升维——下一次你看到 <|im_start|> 这几个字符,记得问一句:它今天进模型时,是几个 token?