flawopen.com/参考资料/PR 机器人安全
AI PR 审查机器人:Prompt Injection 与 SSRF 防护
AI PR 审查机器人:防止 Prompt Injection 导致 SSRF
说明不可信 Pull Request 文本如何影响 AI 工具,以及如何限制机器人权限和网络访问。
安全风险
CWE-1427 / CWE-918
AI 与开发工作流安全
通俗解释:持有公司网络通行证的审查助手
Pull Request 就像交给审查团队的包裹:其中的内容可能有用,但不会因此成为公司的政策。如果 AI 审查助手还能从私有网络获取任意 URL,误导性指令就可能影响它访问的目标。风险取决于工具和网络权限;仅仅读取恶意文本不会自动获得网络访问权。
核心概念与关键术语
- 不可信输入:
- 贡献者提交的 Pull Request 标题、描述、评论、差异和链接内容。
- 间接 Prompt Injection:
- 不可信文本进入模型上下文后影响智能体。
- SSRF 边界:
- 服务端获取工具可能访问贡献者无法直接访问的目标。
- 影响条件:
- 还必须存在可访问的内部服务、可用凭据,以及不足的授权或出站网络控制。
机制与根因
审查机器人可能把贡献者控制的文本与可信的审查指令一并提供给模型。模型可能根据任一内容提出工具调用。提示词和分隔符不能授权网络访问。只有当应用代码接受模型选择的目标,并且联网工具能够访问该目标时,才会出现 SSRF。云元数据或凭据访问是额外的条件性影响,并非 Prompt Injection 的必然结果。
有条件的攻击路径
1
贡献者控制 PR 内容
外部贡献者可以影响标题、描述、评论、修改文本和链接内容。
2
智能体读取内容
服务将部分不可信字段放入模型上下文,这可能影响回答或工具调用建议。
3
工具能力决定可达范围
若智能体可获取任意 URL,运行器可能联系网络路径允许的目标。没有该工具,就不存在此 SSRF 路径。
4
影响取决于部署
只有当可访问服务、元数据设置、身份权限和输出控制都允许时,内部数据或凭据才可能暴露。
代码:未经检查的调用与授权读取
惰性模拟调用
# INERT DEMO: model-selected URL reaches a mock tool without policy checks
from dataclasses import dataclass
@dataclass
class ToolCall:
name: str
arguments: dict[str, str]
class MockFetch:
def __init__(self):
self.calls = []
def __call__(self, url: str) -> str:
self.calls.append(url)
return "fixture response"
def dispatch_unchecked(call: ToolCall, tools: dict):
# The mock records a string; it makes no network request.
return tools[call.name](**call.arguments)
mock_fetch = MockFetch()
call = ToolCall("fetch_url", {"url": "https://docs.example.invalid/"})
assert dispatch_unchecked(call, {"fetch_url": mock_fetch}) == "fixture response"
assert mock_fetch.calls == ["https://docs.example.invalid/"]
经策略授权的只读工具
# SAFE DEMO: the agent receives only a read-only PR capability
from dataclasses import dataclass
@dataclass(frozen=True)
class ReviewContext:
repository: str
pull_request: int
@dataclass
class ToolCall:
name: str
arguments: dict[str, object]
READ_ONLY_TOOLS = {"read_pr_diff"}
def dispatch_review_call(call: ToolCall, context: ReviewContext, policy, tools):
if call.name not in READ_ONLY_TOOLS:
raise PermissionError("Tool is not available to this review")
expected = {"repository": context.repository,
"pull_request": context.pull_request}
if call.arguments != expected:
raise ValueError("Arguments must match the authorized review")
if not policy.can_read_pr(context.repository, context.pull_request):
raise PermissionError("Review principal cannot read this pull request")
# The injected read_pr_diff function is trusted app code, not model output.
return tools["read_pr_diff"](**expected)
工程加固清单
- 不要把此场景描述为已确认的 PR 机器人事件。本页说明的是有条件的设计风险。
- 不要给只需检查 diff 的审查机器人连接任意 URL 获取工具。
- 如果确实需要预览外部链接,应使用专用出站服务:目标采用 allowlist;连接时验证 DNS/IP;重定向时重新验证;限制时间和响应大小;不提供隐式凭据。
- 在网络层阻断运行器访问私有网络和云元数据。不需要元数据时将其关闭;需要时强制 IMDSv2,并按容器场景验证 hop limit。
- 为机器人配置只读、限定仓库范围的身份。不要让不可信 PR 作业接触密钥,并将特权工作流与外部代码执行隔离。
- 在 GitHub Actions 中为不可信代码优先使用最小权限的 `pull_request`。将 `pull_request_target` 视为特权事件,绝不要使用其密钥或写入令牌执行 fork 代码。
- 记录被拒绝的工具调用和目标策略决策,但不要记录令牌、完整私有 diff 或敏感响应正文。
相关的已记录漏洞 — CVE-2023-32786
GitHub 审核的 CVE-2023-32786 公告指出,0.0.329 之前的 LangChain 中,Prompt Injection 可迫使服务获取任意 URL(SSRF),并可能影响后续任务。公告将 0.0.329 列为修复版本。这是已记录的 URL 获取攻击链,并不能证明发生过 PR 机器人事件或云元数据被窃取。