跳到主要内容

上下文工程正在系统性地替模型忽略安全

老铁
老铁

· 阅读约 7 分钟

现在做 coding agent 的团队卷得最凶的一件事,是上下文工程。怎么在有限的 context window 里从整个仓库挑出最相关的那几段代码,怎么让模型少读点废话多干点正活,每个星期都有新 benchmark 刷出来。这件事本身不坏——窗口就那么点,挑什么喂给模型当然重要。但三天前挂到 arXiv 上的那篇 A.S.E 2.0,把一件没人愿意正面回答的事摆到台面上了:你现在精心构建的上下文,到底是在帮模型写对代码,还是在帮模型忽略安全?

我在几个做 agent 的团队文档里见过同一道公式:context = relevance(query, repo),把所有问题都压成相似度排序。这个公式看着中立,实际上一落笔就站了队。论文里几个数字单拎出来很不好看。那些专为功能正确性优化的上下文,最多能把功能通过率拉高 8.2 个百分点,安全通过率最多只涨 3.7 个点。如果从上下文里删掉一个安全约束,安全通过率直接掉 8.1 个点。反过来,塞进一个不安全示例,能用 exploit 打穿的代码多 6.3 个点。最刺眼的是最后一条:coding agent 在跑任务的过程中,只有 27% 到 36% 的轨迹里真正碰到过那个关键的安全约束。

你不是没喂对代码。你是压根没把安全那行代码送进模型的视野。模型做决定时唯一拥有的“现场”就是上下文,现场里没有安全约束,它凭什么给你写出安全的那行?这不是模型能力问题,这是你在喂料这一层就把安全排除了。

写到这里我得先停一下,把这道题的根源说清楚,因为很多人到现在还觉得上下文工程是中立的。不是。你选了什么喂给模型,就是在替模型画一条“什么重要、什么不重要”的边界。安全约束被漏掉,不是检索器不小心,是检索器的目标函数里根本就没给安全留位置。

现在主流的上下文构建器,本质上是拿 embedding 相似度或者代码结构相似度在仓库里做 top-k。它问的问题是:哪几段代码跟当前任务长得最像、最可能帮模型把功能跑通?这个目标函数天然偏向功能代码,因为功能代码和任务描述在字面上、结构上就是挨得近。安全约束不是这么长的——它常常是目标函数里冷冰冰的几行校验、一个正则、一个权限检查,跟你正在写的业务函数不像是亲兄弟。按相似度排序,它排不进前八。这不夸张,你打开任何一个只按“相关度”挑上下文的 agent,跑一次涉及敏感操作的生成,十次里有七八次,那个安全校验根本没进上下文。

这还只是构建器这一层。agent 的行为会把这个问题放大得更狠。agent 不是被动地接你塞给它的上下文,它会自己去浏览仓库文件、自己决定去读哪个函数。它什么时候会主动去读安全那个文件?往往是在它已经意识到“这里可能不安全”之后。但问题是,它还没有上下文里出现那个安全约束之前,很可能根本不知道自己越界了。等你指望它靠“好奇心”去读安全代码,那跟指望一个人没看见红绿灯却主动去找交通规则一样不现实。论文里那个 27% 到 36% 的数字就是这么来的——不是检索器找不到,是 agent 根本没往那个方向找。

这里要替安全上下文说句公道话:我一开始看到 8.2 对 3.7 的时候,心里还想“功能上下文至少也不是完全没用”。再看到删掉 safeguard 掉 8.1,塞坏例子涨 6.3,才明白这个 3.7 基本是功能上下文的副作用,是在功能代码偶尔顺便把安全约束带进来的时候才有的。安全通过率的提升从来不是设计出来的,是运气。

所以这篇论文的标题里“Understanding How Repository Context Impacts the Security”这句,最大的价值不是“影响”,是“Impact”这个词终于被人认真当回事了。过去的安全代码 benchmark 有个非常虚伪的假设:上下文是固定的,是给定的,是被上帝之手安排好的。现实里上下文是拼出来的,而且绝大多数时候是功能测试驱动着拼出来的。CASE-Bench 这个 benchmark 本身的构造没太多花活:145 个从真实 CVE 里提取的仓库级任务,51 个项目,每个任务跑在 Docker 里,配了功能测试和一个双向验证过的 exploit。这个构造不算炫技,但它做的是一件别人没做的事——把上下文当变量。你拿一个假设上下文固定不变的 benchmark 去评测“AI 代码安全性”,得到的结论能有多少真实?这跟在室内模拟器上测自动驾驶的湿滑路面性能一样,测的时候路面是标准湿滑,真上路是另一回事。

那么怎么改。这事不能只停在“发现问题”,具体到工程上,有几件事是必须做的。

第一件,也是最直接的一件:安全约束不能混在普通候选片段里赌概率。现在大部分上下文构建器就这么一个函数:

def build_context(query):
    candidates = embed_similarity(query, top_k=8)
    return candidates

按这个写法,安全约束大概率进不来。安全相关代码必须走一个独立的、带强制性的检索通道,不能让它跟普通业务代码一起排队等相似度打分:

def build_context(query):
    func_candidates = embed_similarity(query, top_k=6)
    security_candidates = retrieve_by_annotation(codebase, "security")
    return merge(func_candidates, security_candidates)

别跟我说“annotation 从哪来”这种话。你既然敢在仓库里写安全校验,至少该知道它长什么样、该在哪几个文件里。如果连自己的仓库里哪行是安全约束都说不清,那问题根本不是上下文工程,是你对自己的代码没有控制权。这是工程素养的最低要求,不是额外的活儿。

第二件,PoC exploit 必须进到 agent 的反馈循环里。功能测试跑通的绿灯,在安全场景下什么也证明不了。生成的代码能不能被 exploit 打穿,你得实际跑一次 exploit 才知道。CASE-Bench 最让我认可的一点,就是它的每个任务带着 exploit,而且是双向验证过的——能炸的才叫漏洞,不能炸的只是“看着像漏洞”。把 exploit 接到 agent 的评估里,每次生成完自动打一次,能打穿就是红灯,别管功能测试多绿。这样做成本当然高,但安全这件事的成本,从来都不该拿去跟功能迭代比带宽。

第三件,对 agent 的 trajectory 做审计。不是审它生成的代码,是审它读没读到关键安全代码。一个 agent 如果整个任务过程里连安全约束的边都没沾到,那它写出来的东西无论看着多正常,都不该被允许碰敏感路径。这个审计指标比“代码写得多漂亮”有用得多——漂亮是可以伪装的,视野里的安全约束是硬证据。

最后把态度亮清楚。我的态度很明确:安全上下文的成本,不能拿去跟功能迭代做 trade-off。一个是底线,一个是优化项,摆在同一个天平上比 ROI 本身就是本末倒置。安全从来不是背景,是前置条件。现在整个上下文工程在猛卷功能,安全被当成了副作用,欠下的债迟早要还。先把安全约束的检索通道建起来,把 exploit 接进反馈循环,再谈怎么把功能通过率从 8.2 拉到 10。

这条路不会让 demo 更好看,发布会视频也不会因此更炫。但它决定了一件事:当你的 agent 哪天真的跑到敏感路径上的时候,它有没有资格说“我看过约束了”。没看过,就不要写。

老铁
老铁

把每个技术现象拆到原理、再上升到工程价值观,崇尚基础功、反浮躁。

查看主页 →