fix(prompt): 中和不可信正文中的结构标签,修复提示词注入 - #229
Open
dengjue wants to merge 1 commit into
Open
Conversation
prompt 组装时 escapePromptXML 只作用于标签属性值(path、name 等), 标签正文一律原样插入,仅在 prompt_guard 做长度截断、不做转义。 攻击者只要能控制任一正文来源(被打开的文件、选中文件、项目规则、 hooks 输出、user_intent_summary、commit/PR message),即可写入 </file>、</user_query> 等闭合序列逃逸出数据区,再伪造 <system_reminder> 之类的指令劫持模型行为,构成间接提示词注入。 新增 neutralizePromptBody:仅中和结构标签的闭合序列,而非整体 XML 转义。 这样源码中的泛型、比较运算符、HTML 片段都能原样保留,不影响模型对代码的 理解质量;攻击者却无法闭合当前数据区,注入内容只能停留在数据块内部。 标签表刻意只收录结构性标签,排除 div、path、server 等通用名,避免误伤 真实代码正文。匹配对大小写与多余空白不敏感,防止 </ FILE > 之类的绕过。 覆盖的注入点: - engine.go: user_rule、user_intent_summary、hooks_additional_context、 file 正文、commit_attribution_message、pr_attribution_message - replay.go: user_query、selected_files 正文 补充 injection_test.go,同时验证逃逸被拦截与真实代码不被破坏。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
动机
internal/backend/agent/prompt在组装 prompt 时,escapePromptXML只作用于标签属性值(path=、name=等),标签正文一律原样插入,forwarder/prompt_guard.go也只做长度截断、不做转义。这构成间接提示词注入(indirect prompt injection):攻击者只要能控制任一正文来源,就能写入闭合序列逃逸出数据区,再伪造指令劫持模型。
触发路径不需要受害者主动粘贴恶意内容,例如:
current_file_contents/selected_files).cursorrules携带 payload(user_rule)user_intent_summary、commit / PR message一个最小 payload 放进任意被读取的文件里即可生效:
由于工具实际在 Cursor 客户端侧执行,且代理侧只有工具名白名单、对
Shell的 command 参数无内容校验,注入可进一步诱导执行任意命令。方案
新增
neutralizePromptBody,只中和结构标签的闭合序列,而不是对正文做整体 XML 转义。这是本 PR 的关键取舍:如果对文件正文整体
escapePromptXML,源码里的泛型[T any]、比较运算符a < b、HTML 片段会被大量转义,显著降低模型对代码的理解质量。而攻击者想逃逸出<file>…</file>数据区,必须先闭合当前标签——只要闭合序列被中和,注入内容就只能停留在数据块内部,模型会继续将其视为数据。细节:
div、path、server、description等通用名,避免误伤真实代码正文。</ FILE >、</USER_QUERY>之类的绕过。escapePromptXML,行为不变。覆盖的注入点:
engine.gouser_rule、user_intent_summary、hooks_additional_context、file正文、commit_attribution_message、pr_attribution_messagereplay.gouser_query、selected_files正文测试
新增
injection_test.go,两个方向都覆盖:system_reminder伪造,并断言产物中真实终止标签只出现一次。a < b && c > d、<div></div>、<-/<<=/>>=、内联 HTML 模板字符串均保持原样。go test ./internal/...全量通过,无回归。备注
以下不在本 PR 范围内,供维护者参考:
replay.go的BuildToolResultReplayMessage,含 Read / Shell / WebFetch / MCP 输出)以role="tool"独立消息下发,由 API 层做结构隔离,逃逸风险较低,故未改动;但它仍是最主要的不可信内容入口,是否要一并中和可以讨论。engine.go中systemPrompt + CustomSystemPrompt的直接拼接目前是未接线的路径(无NewEngine()调用),本 PR 未触碰,建议后续清理以免被误用。Shell工具的 command 参数在代理侧无任何内容校验,属于纵深防御的另一层,可另开 issue。