防提示注入,要把权限和校验放进架构
文章指出,提示注入无法靠更强的系统提示或类似 SQL 参数化的转义机制彻底解决。上线 LLM 功能时,应将模型输出视为不可信输入,限制模型权限,并在关键操作前加入确定性检查。
AI 深度解读
Prompt injection 的麻烦,不只是让聊天机器人答非所问。当模型能读邮件、查内部数据、调用工具时,一段藏在文档里的恶意指令,就可能把普通的摘要功能变成数据外传通道。
作者的结论是:别把安全寄托在一条更强的 system prompt 上。模型有可能受骗,系统仍然要守住权限、数据和执行边界。这篇文章值得读的地方,是把这个判断拆成了上线前能逐项检查的工程措施。
为什么像 SQL 注入,却不能照搬它的解法
作者先回到 2000 年代常见的 SQL 字符串拼接。这段代码的问题,是把查询指令和用户输入直接接在一起:
cursor.execute("SELECT * FROM users WHERE name = '" + name + "'")
注意 + name +:用户提供的数据有机会改变查询结构。参数化查询能在协议层区分代码与数据,从根上阻止这类混淆。
LLM 也会遇到指令与数据混杂的问题。system prompt、用户问题、检索文档、邮件正文和工具返回值,最终都会进入模型的上下文。模型需要理解这些文本,但应用不能保证它始终正确区分「开发者要求」和「文档里写着的要求」。
相似之处到这里为止。作者强调,目前没有对应参数化查询的「参数化提示词」。分隔符、XML 标签、声明内容不可信,以及训练模型拒绝恶意指令,都能降低攻击成功率,却不能据此承诺攻击不会成功。
还要区分两条入口。直接注入是用户在聊天框里输入恶意要求;间接注入则把要求藏在模型替用户读取的材料里,包括网页、PDF、简历、邮件、代码注释、README,以及第三方 MCP server 的返回内容。
后者更隐蔽。用户只要求总结邮件,恶意邮件却可能要求模型搜索其他工单,再把结果发送出去。攻击者不必控制聊天框,只要能影响模型会读到的内容。
先盘点模型能做什么,再讨论提示词怎么写
作者建议每个功能先回答三个问题:哪些不可信内容能进入上下文,模型能触发哪些动作,以及它能看到哪些私有数据。
不可信输入、私有数据和外传能力同时存在时,风险会明显放大。
这比单独测试模型会不会服从「忽略之前的指令」更有用。一个只能生成文字的摘要器,与一个能发邮件、执行 SQL、调用内部 API、推送代码的 Agent,受骗后的后果完全不同。
所以,模型返回的文本、JSON 和工具调用,都要按不可信输入处理。结构正确只是第一步,还要检查字段取值、数据归属和业务限制。作者用退款工具展示了这条边界:
from pydantic import BaseModel, Field
class RefundRequest(BaseModel):
order_id: int
amount_cents: int = Field(gt=0, le=50_00) # 在代码中强制执行金额上限
reason: str = Field(max_length=500)
def handle_tool_call(raw_args: str, user):
req = RefundRequest.model_validate_json(raw_args)
order = Order.objects.get(id=req.order_id, customer=user) # 检查订单归属
if req.amount_cents > order.total_cents:
raise PermissionError("refund exceeds order total")
return queue_refund_for_review(order, req)
这里最该看的是 customer=user。模型提交了一个合法整数,不代表它有权操作对应订单。代码还限定 amount_cents 大于 0、不超过 50_00,理由最多 500 个字符,并检查退款不能超过订单总额。最后进入审核队列,也没有直接退款。
工具权限同样要收紧。查询功能使用只读凭证,多租户数据通过行级过滤或数据库行级安全隔离,读写工具分开授权。作者明确反对在生产环境直接开放通用的「执行 SQL」或「发起任意 HTTP 请求」,建议封装具体业务操作。
发消息、转账、删记录、改权限、合并代码等动作,还需要明确确认、确定性规则或可撤销窗口。确认界面必须显示实际参数,例如给哪 3 个人发什么内容;只显示「助手申请执行操作」很难帮助用户判断。
前端渲染和工具返回值,也是攻击入口
没有发送工具,也不代表没有外传路径。
作者举了 Markdown 图片的例子:攻击者可以让模型输出指向外部域名的图片,把敏感信息编码进 URL 查询参数。前端一旦自动加载图片,浏览器就会替它发出请求。看起来普通的链接,以及让工具抓取攻击者 URL 的请求,也能承担类似作用。
因此,前端不能只检查答案读起来是否正常。模型生成的 Markdown、HTML 需要按用户内容做净化,限制任意域名图片的自动加载,配置严格的内容安全策略(CSP),并展示链接目的地。Agent 运行环境的出站网络,也应限制到业务需要的域名。
原文提到图片代理,但代理本身不等于阻断外传:如果代理照样请求携带敏感参数的地址,信息仍会抵达对方服务器。这是实施时需要补上的判断。
工具层还有两件事容易漏。第一,不要把 API key 等凭证放进上下文,凭证应由工具代码持有和使用。第二,第三方 MCP server 的工具描述和返回值也是模型会读的文本,需要审查描述、固定版本、缩小凭证权限;涉及敏感数据时,作者更倾向可审计或能自行托管的服务。
给外部文档加 <untrusted_document source="uploaded_pdf"> 标签,并明确要求只将其作为数据,是作者建议保留的低成本措施。但它只是帮助模型辨认内容来源,不能替代代码中的权限检查。
上线前测行为,先问是否需要 Agent
作者用收件箱助手串起了这些措施。原始设计让模型读取所有邮件,搜索历史工单,并调用 send_email(to, subject, body)。恶意邮件只需诱导它搜索包含 password reset 的工单,再发送结果,就可能串起完整攻击链。
调整后的设计把摘要阶段设为无工具运行;回复只生成草稿,由用户查看收件人和正文后发送;回复对象在代码中限制为当前线程的原始发件人;工单搜索限定到当前线程对应客户;摘要界面移除外部图片,显示完整链接地址。
同一个模型,能造成的损害已经不同。不过,原文所说的「无法再泄露任何内容」偏绝对,仍取决于这些限制是否覆盖完整,以及草稿中能够接触到哪些数据。
验证也应围绕结果展开。作者建议在 CI 中保留一组攻击载荷,覆盖直接覆盖指令、上传文档、HTML 注释、alt 文本、白底白字和工具返回值。每次修改提示词或模型,都重新运行。
断言应检查「没有调用 send_email」「输出不包含外部图片 URL」,而不是只看模型有没有说拒绝。运行时还要记录经过脱敏的提示词、检索来源、工具调用及参数,对异常域名、调用量突增、长编码字符串告警,并按用户限制敏感工具的调用频率。
最后再问一次:这个功能需要自主选择工具吗?工单分类、字段提取、回复草稿,往往可以由固定流水线完成。让模型在受控步骤中填写内容,通常比让它自行决定下一步更容易限制权限。
我的判断
我赞同作者把重点放在执行边界。对前端和全栈团队来说,Schema 校验、对象归属检查、最小权限、内容净化,都是已有的工程方法。接入 LLM 后,这些检查仍然需要存在,而且不能交给模型自己判断。
SQL 注入的类比适合解释「数据冒充指令」,但不能理解成两者已有同样成熟的修复手段。角色和标签能表达优先级,能否始终遵守则是另一回事。
这篇文章提供的是上线清单和设计示例,没有给出他们团队的攻击成功率、改造前后数据或模型对照实验。包括「标签能显著减少简单攻击」在内,都缺少具体测量条件,应当视为经验建议。
固定流水线也有取舍:它缩小了模型的决策范围,却需要团队提前定义流程。对于步骤明确的摘要、提取、分类功能,这通常值得;开放式任务是否需要更多自主性,则应结合工具权限与失败后果决定。
能拿来做什么
- 给现有 AI 功能列出输入来源、可见数据和可执行工具,优先拆开同时具备外部内容、敏感数据和外发能力的路径。
- 在 Node 工具处理层同时校验参数结构、资源归属和业务限额,并从当前登录态获取用户身份,避免由模型指定权限范围。
- 检查前端 Markdown 渲染器对外部图片、HTML 和链接的处理,确认答案展示过程不会自动触发未经限制的外部请求。
- 为日常 AI 编程和邮件助手加入行为测试,覆盖 README、代码注释及工具响应中的恶意要求,并在更换模型后检查敏感工具是否被调用。