<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Attack-Surface on iamelli0t's blog</title><link>https://iamelli0t.github.io/zh/tags/attack-surface/</link><description>Recent content in Attack-Surface on iamelli0t's blog</description><generator>Hugo</generator><language>zh</language><lastBuildDate>Sat, 10 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://iamelli0t.github.io/zh/tags/attack-surface/index.xml" rel="self" type="application/rss+xml"/><item><title>AI Agent 安全系列：1. AI Agent 结构拆解</title><link>https://iamelli0t.github.io/zh/ai-agent-security/01/</link><pubDate>Sat, 10 Oct 2026 00:00:00 +0000</pubDate><guid>https://iamelli0t.github.io/zh/ai-agent-security/01/</guid><description>&lt;p&gt;2025 年以来，主流 coding agent 的漏洞通报进入高发期，Claude Code、Codex、Cursor、Gemini CLI、GitHub Copilot 相继出现可由恶意仓库远程触发的严重漏洞。Claude Code 的 CVE-2025-59536，恶意仓库携带的 hooks 配置先于信任对话框执行[1]；Codex CLI 的 CVE-2025-61260，项目本地的 MCP 配置被自动加载，克隆一个仓库就交出执行权与 GitHub token[2]；Cursor 的 DuneSlide（CVE-2026-50548/49），沙箱写入白名单按模型填写的参数构建，注入内容可以让白名单指向沙箱执行器本身[4]。&lt;/p&gt;
&lt;p&gt;Gemini CLI 的 CVE-2026-12537，工作区信任判断与工具白名单被绕过，最终落到容器启动器的命令注入[5]；GitHub Copilot 的 CVE-2025-53773，恶意仓库把 autoApprove 写进设置，agent 直接进入全自动模式[6]。2026 年 9 月，Codex 再披露 Heapjack 与 Overpatch 两个无 CVE 编号的沙箱逃逸漏洞，共同根因是沙箱的强制机制与被它限制的代码处在同一信任域[3]。&lt;/p&gt;
&lt;p&gt;这些漏洞的直接根因各不相同，覆盖执行时序、配置自动加载、沙箱构建方式、信任判断与白名单解析，但失效位置集中在少数几个部件上：配置加载、权限判定、沙箱边界与框架自身代码。失效位置如此集中并非偶然，它与 agent 的内部结构直接相关。要回答“为什么是这几个位置”，需要先把 agent 拆开，弄清它由哪些部件组成、哪些输入能够进入、中间有几道关卡。&lt;/p&gt;
&lt;p&gt;这是 AI Agent 安全系列的第一篇。系列的分析路径是：从内部结构推导攻击面，再从每个攻击面推导攻击路径，每一步用公开的 CVE 与 writeup 印证。&lt;/p&gt;
&lt;h2 id="与传统软件的本质差别输入参与决策"&gt;与传统软件的本质差别：输入参与决策&lt;/h2&gt;
&lt;p&gt;传统软件同样要处理不可信输入：浏览器解析网页，邮件客户端解析邮件，攻击者控制的内容每天都在进入系统。但传统软件的数据与程序是分离的，输入是数据，不参与决定程序下一步做什么。浏览器拿到一段 HTML，按照写定的渲染逻辑绘制页面；攻击者若想利用，必须先找到解析器或内存管理的缺陷，把数据变成控制流，这是漏洞挖掘的传统路径，门槛不低。&lt;/p&gt;
&lt;p&gt;agent 改变了这个前提，网页内容、issue 评论、依赖包里的注释、MCP server 返回的结果，进入 agent 之后都直接参与决定下一步调用哪个工具、传递什么参数，模型把读进的一段文字转成一次工具调用，输入由此从数据变成了决策依据本身。&lt;/p&gt;
&lt;p&gt;两种模型的差别如下图：&lt;/p&gt;
&lt;svg viewBox="0 0 1050 560" width="680" style="display:block;max-width:100%;height:auto;margin:1.2rem auto" role="img" aria-label="Traditional software vs Agent: the data-control boundary"&gt;
&lt;!-- 上下对比:传统软件输入与控制流之间有一道墙,Agent 同位置墙被移除(虚线残影);配色沿用全文体系 --&gt;
&lt;defs&gt;
&lt;marker id="arr3" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6.5" markerHeight="6.5" orient="auto-start-reverse"&gt;&lt;path d="M0,0 L10,5 L0,10 z" fill="currentColor"/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;text x="525" y="42" text-anchor="middle" fill="currentColor" font-size="16" font-weight="700"&gt;Traditional software&lt;/text&gt;
&lt;g fill="currentColor"&gt;
&lt;rect x="60" y="105" width="170" height="64" rx="12" fill="rgba(100,150,220,0.16)" stroke="#6c8ebf" stroke-width="1.6"/&gt;
&lt;text x="145" y="131" text-anchor="middle" font-size="13.5" font-weight="600"&gt;Untrusted input&lt;/text&gt;
&lt;text x="145" y="151" text-anchor="middle" font-size="10" opacity="0.75"&gt;web page · email&lt;/text&gt;
&lt;rect x="300" y="105" width="150" height="64" rx="12" fill="rgba(127,127,127,0.09)" stroke="currentColor" stroke-opacity="0.55" stroke-width="1.6"/&gt;
&lt;text x="375" y="131" text-anchor="middle" font-size="13.5" font-weight="600"&gt;Parser&lt;/text&gt;
&lt;text x="375" y="151" text-anchor="middle" font-size="10" opacity="0.75"&gt;strict format&lt;/text&gt;
&lt;rect x="530" y="105" width="210" height="64" rx="12" fill="rgba(127,127,127,0.09)" stroke="currentColor" stroke-opacity="0.9" stroke-width="2.5"/&gt;
&lt;text x="635" y="131" text-anchor="middle" font-size="13.5" font-weight="600"&gt;Program logic&lt;/text&gt;
&lt;text x="635" y="151" text-anchor="middle" font-size="10" opacity="0.75"&gt;hard-coded&lt;/text&gt;
&lt;rect x="800" y="105" width="180" height="64" rx="12" fill="rgba(127,127,127,0.09)" stroke="currentColor" stroke-opacity="0.55" stroke-width="1.6"/&gt;
&lt;text x="890" y="131" text-anchor="middle" font-size="13.5" font-weight="600"&gt;Action&lt;/text&gt;
&lt;text x="890" y="151" text-anchor="middle" font-size="10" opacity="0.75"&gt;render · compute&lt;/text&gt;
&lt;/g&gt;
&lt;rect x="486" y="88" width="20" height="98" fill="rgba(212,60,60,0.30)" stroke="#d43c3c" stroke-width="1.8"/&gt;
&lt;g stroke="currentColor" stroke-width="2" fill="none"&gt;
&lt;path d="M230,137 H296" marker-end="url(#arr3)"/&gt;
&lt;path d="M450,137 H482" marker-end="url(#arr3)"/&gt;
&lt;path d="M506,137 H526" marker-end="url(#arr3)"/&gt;
&lt;path d="M740,137 H796" marker-end="url(#arr3)"/&gt;
&lt;/g&gt;
&lt;text x="530" y="82" text-anchor="middle" fill="#d43c3c" font-size="10.5" font-weight="700"&gt;data | control&lt;/text&gt;
&lt;text x="525" y="238" text-anchor="middle" fill="currentColor" font-size="11" opacity="0.8"&gt;attacker must find a parser or memory flaw to turn data into control flow&lt;/text&gt;
&lt;line x1="80" y1="272" x2="970" y2="272" stroke="currentColor" stroke-opacity="0.3" stroke-width="1.2" stroke-dasharray="6 5"/&gt;
&lt;text x="525" y="316" text-anchor="middle" fill="currentColor" font-size="16" font-weight="700"&gt;AI Agent&lt;/text&gt;
&lt;g fill="currentColor"&gt;
&lt;rect x="60" y="385" width="170" height="64" rx="12" fill="rgba(212,60,60,0.12)" stroke="#d43c3c" stroke-opacity="0.7" stroke-width="1.6"/&gt;
&lt;text x="145" y="411" text-anchor="middle" font-size="13.5" font-weight="600"&gt;Untrusted input&lt;/text&gt;
&lt;text x="145" y="431" text-anchor="middle" font-size="10" opacity="0.75"&gt;web page · issue · MCP result&lt;/text&gt;
&lt;rect x="300" y="385" width="220" height="64" rx="12" fill="rgba(127,127,127,0.09)" stroke="currentColor" stroke-opacity="0.55" stroke-width="1.6"/&gt;
&lt;text x="410" y="411" text-anchor="middle" font-size="13.5" font-weight="600"&gt;Model reads content&lt;/text&gt;
&lt;text x="410" y="431" text-anchor="middle" font-size="10" opacity="0.75"&gt;content → decision input&lt;/text&gt;
&lt;rect x="620" y="385" width="180" height="64" rx="12" fill="rgba(247,223,168,0.35)" stroke="#c9a53f" stroke-opacity="0.9" stroke-width="1.6"/&gt;
&lt;text x="710" y="411" text-anchor="middle" font-size="13.5" font-weight="600"&gt;Next tool call&lt;/text&gt;
&lt;text x="710" y="431" text-anchor="middle" font-size="10" opacity="0.75"&gt;which tool · what args&lt;/text&gt;
&lt;/g&gt;
&lt;rect x="486" y="385" width="20" height="64" fill="none" stroke="#d43c3c" stroke-width="1.5" stroke-dasharray="5 4"/&gt;
&lt;text x="530" y="379" text-anchor="middle" fill="#d43c3c" font-size="10.5" font-weight="700"&gt;boundary removed&lt;/text&gt;
&lt;g stroke="#d43c3c" stroke-width="2" fill="none"&gt;
&lt;path d="M230,417 H296" marker-end="url(#arr3)"/&gt;
&lt;path d="M520,417 H616" marker-end="url(#arr3)"/&gt;
&lt;/g&gt;
&lt;text x="525" y="516" text-anchor="middle" fill="currentColor" font-size="11" opacity="0.8"&gt;content directly shapes decisions — no boundary between data and control&lt;/text&gt;
&lt;/svg&gt;
&lt;p&gt;输入参与决策是第一条事实，权限是第二条。coding agent 通常持有开发者本人的完整权限，从读写文件、执行命令到使用其凭证，这种权限是产品前提而非实现上的省事：agent 要执行业务，就必须持有相应权限。两条事实合在一起，构成 agent 安全的核心问题：不可信内容能够影响决策，决策的执行又携带完整权限，中间的防线是什么、能否有效拦截。输入与执行之间的这些防线，正是 Agent 安全研究的对象。&lt;/p&gt;</description></item></channel></rss>