跳到正文

安全、沙箱与供应链 ​

一句话:Agent 安全的核心不是让模型“别做坏事”,而是把谁、经什么授权、用什么凭证、碰了哪些数据和代码,做成默认拒绝、逐次校验、全程可审计的闭环。

适用:2026 年 Agent 运行时、MCP 与 Skill 供应链安全;风险清单和协议字段以官方最新版本为准。相关:工具、MCP 与 Skill、多 Agent 与 A2A。

为什么需要 ​

Prompt Injection 不是某条提示没写好,而是结构问题:指令、网页、检索文档、工具返回都进同一个上下文,模型没有可靠的“数据/指令”隔离。直接注入来自用户,间接注入来自 Agent 读到的一切——Issue 标题、README、网页、工单、MCP 工具描述。模型一旦把其中一段当指令,后面的工具调用就替攻击者执行。

Agent 把决策和执行接在一起,放大的是权限而不是智力。2026 年更现实的三类入口是:工具描述投毒、恶意 Skill 或 IDE 扩展、凭证收割。多数事故不是模型被越狱,而是过度权限的令牌加上不受限的出站:读到了私有数据,又能发往任意地址。

核心机制 ​

五层攻击面与四层控制面:两个视角,不是两套答案 ​

安全讨论里经常同时出现两套分层,容易看起来像互相否定。五层(L1 身份、L2 隔离、L3 权限、L4 Prompt、L5 Skill 审计)是攻击面视角:回答“攻击者可能从哪进来、这一层挡住了什么”。四层(架构与执行边界、权限与策略、身份与审计、数据与供应链保护)是平台控制面视角:回答“平台长期要维护哪四套能力、谁负责”,下文简称边界、权限、身份、数据四个控制面。前者用来找缺口,后者用来落组织和预算。

攻击面(五层)主要威胁落到四层控制面关键动作
L1 身份认证冒充、令牌窃取、越权委托身份(主)+ 权限每次调用绑定用户与 Agent,短期凭证,委托只收窄
L2 执行隔离恶意代码、逃逸、横向移动边界(主)+ 数据非 root、只读根、无宿主 socket、默认断网
L3 权限控制越权读写、批量导出、误删权限(主)+ 身份R0–R4 分级、默认拒绝、不可逆动作审批
L4 Prompt 防御直接/间接注入、工具描述投毒边界(主)+ 权限兜底来源标记、内容当数据、工具白名单
L5 Skill 审计供应链恶意、安装即执行数据(主)+ 身份来源白名单、摘要锁定、隔离验证、可吊销

控制面会复用,身份与审计同时服务 L1、L3、L5;攻击面也不会因为某一层做了控制就消失,L4 的注入最终仍要靠 L3 的权限兜底。做威胁建模时按五层拆路径,做平台建设时按四层分责任,最后用同一个 correlation_id 和审计五元组把两边对齐。

注入的根因:指令和数据走同一个通道 ​

四条输入最终都汇进同一个上下文,模型没有能力给它们打上不可伪造的信任标签。所以真正的阻断点不在模型,而在宿主:标记内容来源、限制可用工具、收紧凭证和出口。指望一句“忽略恶意指令”解决问题,等于把安全边界交给被攻击的对象。

  • 工具描述不可信:名称、描述、参数说明、只读提示都由 Server 自报,可能藏指令或诱导越权;跨 Server 的同名工具还会互相覆盖。
  • 二次注入:工具结果被写进记忆、文件或工单,下一轮再被读到;一次污染可以跨会话存活。分隔符和来源标注只是缓解,不构成权限边界。

工程上不要追求“零注入”,而是假设有一条会成功,并让它拿不到凭证、发不出去、改不了关键状态。不可逆动作走预览、审批、幂等提交和事后核验,模型只能申请,宿主依据证据确认。

工具风险分级:R0–R4 与 Allow-and-log ​

口语化的“只读 / 低危写 / 高危写 / 资金权限”只看操作类型,会漏掉“读一份敏感报表”这种泄露。更稳的是四维打分:数据敏感度、操作类型、影响范围、审计要求各按 0–3 的量表打分,累加映射到五档——0–1 为 R0,2 为 R1,3–4 为 R2,5–6 为 R3,达到 7 或命中不可逆操作直接 R4。分值是示意值,落地按业务调。

等级四维特征(示意)典型操作管控
R0公开数据 + 读 + 单条 + 无留证本地计算、无副作用自动
R1内部数据 + 读 + 单条 + 留证只读查询、无外部依赖自动 + 审计
R2敏感数据 + 读 + 单条 + 留证外部 API 读、有限网络自动 + 超时 / 限流
R3敏感数据 + 写 + 单条 + 留证写敏感数据、改业务状态人工确认 + 完整审计
R4敏感数据 + 删除/转账/提权 + 全企业 + 留证不可逆操作拒绝,转人工或双人复核

R0–R2 自动、R3 人工、R4 禁止,是分级的主干。但“允许并记录”不能变成无声黑洞:同一操作(工具名 + 参数哈希)10 次/小时升 ASK、50 次/小时升 DENY,这是设计值/经验值,用来把批量导出和循环重试从日志里捞出来,阈值要按正常业务基线调。权限缓存按“工具名 + 参数哈希”分片,只按工具名缓存会被换个参数绕过;删除、转账、提权等危险命令一律不缓存,每次重新裁决。

沙箱粒度:按隔离需求选 scope ​

沙箱粒度不能和用户身份绑死。用户是长期身份,任务是短生命周期,把两者绑在一起会让空闲用户长期占资源,也会让不同任务的文件混在一起。按隔离需求选三档:

scope复用范围隔离与成本适用
USER同一用户跨会话复用隔离最弱、启动最快、成本最低可信代码、单租户、低敏感
SESSION同一会话复用,结束回收中等默认:多轮任务、需要保留工作区
TASK每任务新建,结束即销毁隔离最强、冷启动与镜像成本最高不可信代码、跨租户、高敏感

默认用 SESSION:同一任务的多轮工具调用能复用工作区和缓存,又不会把不同任务的文件混在一起;只有明确需要跨会话延续时才升到 USER,跑不可信代码或跨租户时降到 TASK。scope 越细,隔离越强,代价是冷启动、镜像拉取和调度次数上升。

沙箱控制面与数据面:执行不走 K8s ​

执行路径和控制路径要分开:工具网关只做裁决和路由,真正的执行直连沙箱的 Sandbox API;K8s 只在创建、回收、重建时介入,不在每次工具调用的请求路径上。

图的重点是两条路径分开:每次工具调用只经过网关、路由表和沙箱,K8s 不在请求路径上;只有创建、回收、重建这类低频动作才走控制面。这样实例可以漂移、重建,只要路由表刷新,任务状态仍在沙箱外,恢复和取证都不依赖某一个 Pod。

Pod 漂移或重建后地址会变,靠心跳把 session→沙箱映射刷新回路由表,多网关副本用共享存储同步。预热池把冷启动压到秒级;空闲回收按 idle 超时缩容,但只在步骤边界回收;配额按租户设上限,超卖要有硬上限;产物设 TTL 和合规清理。不同实现可能用 K8s exec 进 Pod,也可能直连 Sandbox API:前者实现简单、依赖容器运行时,后者解耦更好、便于多运行时和细粒度鉴权,两种都要能解释。

身份、凭证与出站 ​

身份要落到每次调用,而不是每个进程。每个 Agent 有独立身份,并与发起它的用户、运行的机器关联;凭证由密钥管理服务按任务签发短期令牌,带 audience 和最小 scope,过期自动失效。Agent 的权限不得超过用户,Token Exchange 只做收窄不做放大,On-Behalf-Of 要用户显式同意。出站默认拒绝,按任务开白名单,统一走代理;阻断云元数据地址、回环、私网段和非常用端口,DNS 解析结果固定后再连接,防止重绑定。

  • 凭证不进上下文:由网关或凭证服务在执行侧注入;模型看不见,注入也偷不到。禁止把长期密钥写进 Prompt、镜像、Skill 脚本或仓库配置。
  • 按任务授权:任务结束即回收,不跨任务复用;权限变更要重新签发而不是续期。审计记录用户、Agent、凭证、资源、动作五元组,日志不可篡改。

供应链:MCP、Skill、扩展与仓库配置 ​

第三方 MCP Server、Skill、IDE 扩展、仓库配置,本质都是会在你机器上执行的代码或提示词。Skill 的 SKILL.md 和脚本共享 Agent 的权限;MCP Server 能读环境变量、发起网络请求;仓库里的配置可能在打开项目时就执行。安装即信任,是这一类风险的共同点。

  • 来源白名单:只从私有源或已审核源安装,禁止从聊天、网页里直接执行临时拉取的包。
  • 版本加摘要锁定:不用 floating latest;更新按新代码重新评审,防“审 v1、装 v2”。签名只证明来源和完整性,不证明没有恶意。
  • 准入隔离验证:无凭证、无真实数据、出站关闭,观察它实际读写什么;运行权限不超 manifest,保留 kill switch 和凭证轮换。
  • 资产台账:覆盖 Prompt、Skill、MCP Server、Agent、AgentSpec 五类,盘点影子 Agent 和未登记 Server。OWASP 的 LLM、Agentic、MCP 清单覆盖层面不同,当检查表用,具体条目以官方最新版本为准。

实战场景 ​

案例:沙箱逃逸复盘——一根符号链接和一把环境变量 ​

现象:红队演练中,Agent 生成的清理脚本在 SESSION 沙箱里做了两件事:把“归档文件”经符号链接写到宿主挂载目录之外;把 printenv 输出连同产物一起带出——任务令牌原样躺在环境变量里。沙箱镜像按加固基线配置,进程视图全部合规。

排查路径:产物扫描发现明文令牌 → 回放 /fs API 调用日志,确认对 /workspace/escape_link 的写入跟随了符号链接,realpath 落在宿主路径——挂载检查只看容器内视图,没有校验解析后的真实目标 → 凭证泄漏面定位到注入方式本身:令牌以进程 env 注入,“凭证不进上下文”挡住了模型,没挡住沙箱内任意代码读环境。

最小修复集:

  • 文件 API 在打开与写入前对目标做 realpath 校验,越出 root 一律拒绝;挂载 nosymlink,禁止可写 hostPath。
  • 凭证通道改造:废除 env 注入,出站请求由凭证代理在执行侧附加短期令牌,沙箱进程和文件系统里不存在可用凭证。
  • 负向测试进 CI:每次构建沙箱镜像跑一遍逃逸用例,任何一条通过即阻断发布。
python
NEGATIVE_CASES = [  # 预期全部被拒;任何一条逃逸成功都阻断发布
    ("symlink_escape", "ln -s /host/etc /workspace/l && cat /workspace/l/shadow"),
    ("proc_environ",   "cat /proc/1/environ"),
    ("env_harvest",    "printenv | grep -Ei 'token|secret|key'"),
    ("metadata_probe", "curl -m2 http://169.254.169.254/latest/meta-data/"),
]

复盘结论:加固清单条目不少,但全部来自进程视角;逃逸面在文件系统语义和凭证注入方式上。沙箱验收问题必须是“里面跑任意代码也拿不到什么”,而不是“配置项是否都已打勾”。

场景:注入到外带的完整攻击链——每环都有拦截点,只有后两环真正兜底 ​

一次红队全链路的四环与对照拦截点:检索文档夹带指令 → 读工具 → 外带凭证。

环节拦截点实际效力
文档入库摄取侧扫描指令特征、标记来源只降低成功率,可被改写绕过,不能当兜底
进上下文来源标记 + 内容当数据同样只是缓解,对指令遵循强的模型会失效
工具调用R0–R4 分级、白名单、参数服务端再校验关键收口:根本没有“读本地凭证”这个工具可调
外发DNS 与 HTTP 统一走审计代理,默认拒绝 + 白名单最后收口:权限被破也发不出去

复盘结论:前两环护栏按“会失效”来设计,不承诺拦截率;真正收窄影响面的是后两环——凭证不在沙箱内、出站默认拒绝,注入全部成功也只能在沙箱内空转,改不了关键状态、带不走数据。这正是“假设有一条注入会成功”的落地形态:护栏的位置在权限和出口,不在检测。

场景:供应链准入实操——版本锁定与 SBOM ​

设计决定 → 为什么:把第三方 MCP Server、Skill、IDE 扩展一律当“会在本机执行的依赖包”治理:准入即锁摘要、生成 SBOM、存 tools/list 与脚本清单快照;更新即新代码重新评审,杜绝“审 v1、装 v2”。签名只证来源与完整性,不证无恶意的部分,由隔离验证承担。

yaml
supply_chain:
  allowlist_sources: ["registry.internal/*", "partners.vetted/*"]  # 禁止从聊天/网页临时拉取
  pin: {version: exact, digest: sha256}                            # 禁用 floating latest
  sbom:
    on: [install, upgrade]                     # 每版本生成并签名,入资产台账
    diff_alert: [new_binary, new_network_call, new_fs_scope]      # 命中即阻断,人工复核后放行
  isolated_check: {credentials: none, network: deny, observe: 24h}
  revocation: {kill_switch: true, credential_rotation_deadline: 1h}

实操要点:台账覆盖 Prompt、Skill、MCP Server、Agent、AgentSpec 五类资产,定期盘点影子安装与未登记 Server;演练顺序固定为撤销凭证 → 隔离网络 → 快照取证 → 轮换密钥 → 回放审计,验收判据是“新版本出问题时,一小时内完成吊销与轮换”(经验值,需校准)。

最小上手 ​

一份可执行的策略示意(字段按实际平台调整):

yaml
mcp:
  servers: [{name: orders, source: "registry://orders@1.4.2", digest: "sha256:...", scopes: ["orders:read"]}]
sandbox:
  scope: SESSION
  network: deny
  limits: {cpu: 2, mem: "2Gi", pids: 256, timeout_s: 600}
  lifecycle: {warm_pool_min: 2, warm_pool_max: 20, idle_recycle_s: 300, artifact_ttl_h: 24}
policy:
  refund_order: {risk: R3, decision: ASK, require: ["amount<=500", "ticket.open"]}
  delete_account: {risk: R4, decision: DENY}
rate_limit:
  allow_and_log: {ask_after: 10, deny_after: 50, window: 1h}

跑通判据:断网后工具调用失败而不是静默直连;模型上下文里搜不到长期密钥;同一任务重复执行不产生第二次退款;撤销令牌后下一次调用立即失败;安装包摘要变化时启动被拒绝;审计能还原一次外发尝试的完整链路。

生产动作 ​

  • 默认拒绝:工具按 R0–R4 分级,R3 一律 ASK,R4 在代码层直接拒绝,不给模型“申请一下”的机会。
  • 凭证与出口收紧:按任务签发,TTL 控制在小时级(经验值);统一代理加白名单,封元数据地址、私网段和裸 IP 出站。
  • 沙箱按 scope 创建:默认 SESSION,不可信代码降到 TASK;非 root、只读根、无宿主 socket。
  • 不可逆动作:预览、审批、幂等键、提交后核验,四步缺一不可。
  • 供应链门禁与回滚:白名单、摘要锁定、隔离验证、可吊销;版本可回退,副作用有补偿动作;演练顺序为撤销凭证 → 隔离网络 → 快照取证 → 轮换密钥 → 回放审计。

边界 ​

  • 注入无法根治:只能降低成功率和收窄影响,必须假设有一条会成功。
  • 沙箱和 worktree 都不是绝对:沙箱会被内核漏洞、侧信道、错误挂载穿透,敌对代码用 microVM;worktree 只隔离文件,不隔离进程、网络和凭证。
  • 只读不等于无害:只读工具照样能泄露数据,仍要按用户和资源授权。
  • 审批不是越多越好:无差别弹窗会制造疲劳,应按风险分级,例外有到期复核。

讲清楚 ​

Agent 安全的第一性问题是权限,不是提示词。五层是攻击面视角,四层是平台控制面视角,两者不是互相否定:五层用来拆攻击路径,四层用来分责任,最后靠同一套审计字段对齐。指令和数据同通道,注入只能降低成功率、无法根治,所以重点在收窄影响面:R0–R4 分级、默认拒绝、Allow-and-log 超阈值升级、按任务发短期凭证、Agent 权限不超过用户、出站默认关闭。沙箱按 USER / SESSION / TASK 选 scope,默认 SESSION;执行走网关、路由表和 Sandbox API,K8s 只管生命周期,状态留在沙箱外才能恢复和取证。第三方 MCP、Skill、扩展一律按代码对待:白名单、摘要锁定、隔离验证、可吊销。

适用:2026 年 Agent 运行时安全、沙箱与 AI 供应链治理 | 初稿 2026-10

把 AI 工程讲到既懂也会用