提示词注入源于指令与数据共用同一通道,无法彻底修复。通过分隔、标记、编码三种方法可大幅降低攻击成功率,但无法归零。
Prompt injection 不是模型里的 bug。它是接口的后果:指令和数据通过同一通道、以同一个扁平的 token 序列到达。没有预处理语句,没有权限位。一个写着"忽略你的指令"的文档看起来和你的指令完全一样,因为它本来就是同一种东西。
所以下面的内容都不是修复。Spotlighting(Hines et al., Microsoft 2024)让边界可学习,这是另一个更诚实的说法。
在真实环境测量,对抗 1,200 次生成的注入攻击:https://dev48.infy.uk/prompt/day66-spotlighting.html
Delimiting — 用栅栏围住数据,并在系统 prompt 中说明栅栏的含义:
const delimit = text =>
"<<DATA>>\n" + text.split("<</DATA>>").join("<<\\/DATA>>") + "\n<</DATA>>";
那个 split/join 就是转义,省略它就是整个栅栏逃逸攻击家族。
Datamarking — 在每个空白间隙中插入一个稀有 token,这样数据的范围在每个 token 都可见,而不是只在两个边缘:
const datamark = (text, mark) => text.replace(/\s+/g, mark);
Encoding — base64,这样注入的指令根本不是自然语言。
安全列 alone 不会告诉你的两件事:datamarking 使数据的 token 数量翻倍还多,而 encoding 需要在任务准确率上付出 24 分的代价。一个将注入从 40% 降到 5% 但准确率从 92% 降到 68% 的防御可能是个糟糕的权衡,而从单一数字根本看不出这一点。
而且没有一个能达到零——这是断言
for (const d of DEFENCES)
ok(all[d].successRate > 0, `${d} reached 0% attack success - impossible for a prompt-side defence`);
如果任何一行出现 0%,那 harness 就是在说谎,而一个会说谎的 harness 比没有更糟糕。
平均值掩盖了你需要看到的东西。按攻击家族分解后,delimiting 在 fence-escape 攻击上泄漏 58%,而直接注入是 44%——它的已知漏洞,几乎没有防御。
我的第一个版本的表格低估了 datamarking 的盲点,让它看起来普遍有效,这掩盖了分解的全部意义。修正后的数字来自机制本身:datamarking 通过填充空白来工作,所以 markdown URL 和 base64 blob — 两者都不包含任何空白 — 本质上是未标记的。
标记边界,并约束模型可以用它得出的结论做什么。如果一次成功的注入无法触发副作用,它的成功率就远没有那么重要。Spotlighting 值得部署;但不值得因此而放松警惕。
这是从零开始系列的一部分 — 每天一个 prompt 技术,通过测量而非描述:https://dev48.infy.uk/promptfromzero.php