Newshot

身体:把信任层装进一个真实平台——88,000 行怎么验收

Summary

系列《给 AI 上缰绳:从周末 MVP 到多路指挥台》第 3 篇。 上一篇交完底:v2 的信任层——授权合同、围栏、四段链——在测试环境里全绿,但它有个尴尬的处境:独立存在。心脏再健康,没装进身体,就不会跳。信任层要证明自己被用起来了,就得跑在一个真实的 Agent 平台上,被真实任务连续捶打。 10 月 1 日,v3 开工。这篇讲的就是这具身体:怎么从零搭起来、工程

Original text(via FreeBuf)

系列《给 AI 上缰绳:从周末 MVP 到多路指挥台》第 3 篇。

上一篇交完底:v2 的信任层——授权合同、围栏、四段链——在测试环境里全绿,但它有个尴尬的处境:独立存在。心脏再健康,没装进身体,就不会跳。信任层要证明自己被用起来了,就得跑在一个真实的 Agent 平台上,被真实任务连续捶打。

10 月 1 日,v3 开工。这篇讲的就是这具身体:怎么从零搭起来、工程量多大、信任层怎么嵌进去,以及——这部分篇幅最大——实战里踩出来的每一个坑。

先交代口径

一句话定位:v3 是一个受治理的渗透测试 Agent 平台。对话式 Agent 在 Kali 沙箱里执行攻击动作,治理层约束每一次越界,最终产出可复核的 PTES 报告。v2 那套信任层没有重造一遍,它变成了这个平台的一个器官。

体量先摆出来:自有代码 382 件、88,712 行,占总量 85.2%(总量 465 件 / 104,118 行,vendored 第三方资产单列、不计入自有)。测试基线:后端 904 passed + 24 skipped(928 项),双前端合计 124 个用例,全绿。

出处:v3 仓 README(核心指标表 + 平台进化节,归属盘点 2026-10-12 实跑口径)。

时间账:10 月 1 日开工。头三天立地基——分层骨架、沙箱、治理接入、测试基线、记忆系统;10 月 4 日定工程路线,5 日五批连打(四批同日批结),6 日终验收官、git 建仓;10 月 7 日起进入真实任务。从开工到 git 首个 commit,六天。

搭建:这具身体怎么立起来

这次的工程纪律比前两代都重——每个工程批次动工前,先落一份批内细案:这批建什么面、验收线是什么、动哪些文件。细案我批了才动工,收口拿数据说话。

动工前的选型,四个决策。

后端骨架。Flask,五层:api 管协议、services 管业务、models 管持久化、agents 管 Agent 的工具与权限、core 管横切配置。分层的理由很俗:平台代码要长期演化,层不清楚的地方就是将来出事故的地方。Agent 相关的东西单独一层,因为它动得最凶——全平台动得最凶的三件活代码里,工具面、权限面在这层,会话运行时在 services 层。

数据面。四个中间件各管一摊:MySQL 管业务与审计——关系数据加事务保证,审计链的落链点在这里;Redis 管实时事件——Agent 执行过程的事件流由它承载;Qdrant 管向量检索——记忆与经验卡的语义层;Neo4j 管图——资产与发现之间的攻击图关系。为什么不用一个库包打天下:审计要事务、事件要低延迟、检索要向量、攻击图要图查询,四种负载挤一个库,每一种都别扭。v2 的黑板没有消失,拆进了这套结构里。

执行面。这个决策是 v1 的教训直接换来的。v1 用 SSH 连一台远程 Kali:交互式工具最别扭——nmap 扫到一半要等、sqlmap 跑起来要盯输出,全在 SSH 通道里硬模拟;环境还不隔离,每次作业在同一台机器上叠状态。v3 换成 Docker 容器沙箱:每个交战独立容器、独立作战网络,Agent 在 tmux 会话里跑交互式工具——交互式工具本来就是 tmux 的主场。隔离从"约定"变成硬保证:沙箱网络里只有授权目标可达,越界在网络上就不通。

前端双端加 BFF。用平台作战的人和管平台的人是两类角色,两种界面:用户端是作战工作台——对话流、沙箱、审批;管理端是治理面——审计、用户、配置。中间一层 BFF 做实时推送网关:Agent 的执行过程以事件流推到前端,作战的人看得见它每一步在干什么——不是事后翻日志,是直播。

v2:独立信任层v3:完整平台
授权合同(令牌·四元组)client-web 用户端 · admin-web 管理端
黑板 / 网关 / 工具层BFF 实时推送网关(WS 事件流)
熔断 / 审计哈希链Flask 后端(API·服务·模型·智能体·核心)
87+25 测试集MySQL · Redis · Qdrant · Neo4j
(独立库,测试环境里跑)Kali 沙箱容器(tmux · 独立作战网络)
—信任层=后端治理面,嵌在三条链路上
出处:v2 侧见 fence 仓(第 2 篇已引);v3 侧见 v3 仓 README 架构节。

选型定完,v2 信任层的接入点也定了。它是本篇的主角,嵌在三条链路上:

交战创建时。授权文件(RoE)上传、边界固化。固化那一刻铸造签名令牌,客户合同哈希锚定进令牌;同时往审计哈希链上落一条 roe_frozen 事件,载荷里带边界原文的 SHA-256——事后改写边界原文,链上的哈希对不上,一眼假。链写失败呢?整个固化事务回滚。fail-closed:治理动作不落链,就不许成立。

动作执行时。沙箱网络执法,v2 的 87 例越界拦截测试口径原样搬进平台——同一套执法面换了宿主。中间还有一道人工闸:高危动作在执行前拦下来等人放行(HITL 审批),Critical 级的放行必须填理由,空理由的放行请求直接 422 拒回,审计状态不动。

交战收尾时。run 终局捕获证据包:审批记录、审计链、决策轨迹、交战配置等七个条目打成包,manifest 加签,报告中心里一键复算验证。最终产出双签的 PTES 报告——37.6KB 的实物文件,不是模板截图。

地基期立的还有两样。一是记忆系统:三层记忆(会话内 local / 项目 project / 跨项目 user)加经验卡蒸馏——Agent 打完一仗,值得留下的东西自动沉淀成卡片,喂给语义检索,下一仗能查。二是测试基线:动工头一批交付的不是功能,是回归测试——之后每一批工程都在这条基线上收口,只增不减。当代码增速远超人工能读完的速度,"先有尺子再动工"不是洁癖,是必需品。

五批工程:88,000 行怎么验收

骨架和地基立起来之后的问题:功能面怎么建、建完怎么证明每一面都过硬?

88,000 行的量级带来一个前两代没有的问题:我读不完每一行。读不完,验收就不能靠读,得靠门禁——每个工程批次的收口标准是机械的,门禁过了才算过。

我的做法是五批工程,逐面建、逐面过堂:鉴权面、管理端、用户端、运行时核心、最后收口终验。一批一个面,每批同一条验收纪律,三条:

  • 全量回归零失败——批前锚定基线,只增不减,到收官锚在 928 项;
  • 功能矩阵逐行打钩——动工前把全部功能面登记成矩阵,每批完成后逐行核对,任何功能面不得丢失或降级;
  • 更强证明点——每个过堂的面至少拿出一个可测试的更强证明,凭测试或实测数据呈报,不接受"感觉更好了"。

鉴权批。全平台统一 JWT 签发、验签、刷新,三角色 RBAC。更强证明点是那张矩阵:111 个端点 × 7 种身份态(admin / operator / auditor / 未登录 / 过期 / 伪造签名 / 已拉黑)全组合测试,越权请求 403 且留审计痕。另加一道结构性守门:启动时对账鉴权矩阵与代码装饰器,对不上直接拒启。新端点忘了登记权限,服务起不来——防的是"漏配"这种最无聊也最常见的事故。

运行时核心批。主战场,三巨头:会话运行时(1,940 行)、工具面(1,491 行)、权限面(1,084 行)。这批的更强证明点是可证明性本身——决策轨迹全程留痕,每个"决策→工具→参数→结果"进 decision_trace;预算熔断从"会话中断了"升级为可审计遥测,stop_reason 和用量落库。顺带一个架构决策:平台最初有个独立的 kill-chain 编排器,判定为与运行时职责重叠后整体退役,2,006 行删除。删功能也是工程——删错比写错难挽回,所以删除走留档加回归全绿。

最后一批收口做的是开源就绪:脱敏三判据(凭据 / 敏感路径 / 邮箱占位公开面零命中)、.env 全键占位(缺省值全是 CHANGE_ME,启动告警)、git 历史干净——先脱敏、后建仓,敏感信息零进历史。v3 仓至今保持"planned but not yet done"的开源状态:就绪了,还没开。这是如实声明,不是欲擒故纵。

出处:内部五批复执档案(2026-10-05/06);v3 git 首 commit edb5be2(2026-10-06,开源就绪态闭包)。

88,000 行里我逐行读过的可能不到一成,但每批收口时,矩阵、回归、证明点三道门是机械的。人不读代码,人定门禁。门禁错了全错——这个风险的交代放在失效边界。

淬火:实战里踩出来的坑

10 月 7 日起平台跑真实任务。头几天是功能增强(检索优化、模型解析链路),从 12 日开始进入修复高峰:12 到 14 日,三天,40 多个 commit 落库。这一节的故事全部来自这段时间。

毒记忆:它记住了"工具坏了"

平台有 Task 系列工具(建任务、报进度),跑在宿主环境上。某天宿主环境出问题——缺了执行文件,Task 工具调用报错。这是个普通故障,我修了:平台接管 Task 工具,宿主侧短路。

修完发现没用。Agent 起跑后照样声明"TaskCreate 面板因宿主故障不可用(与既往一致)",直接绕开规划工具。查它的记忆库,找到了:那次故障被它写进了跨 run 的持久记忆层,五条,是之前几个 run 攒下的。系统提示里写得明明白白的规划教导,被这几条旧记忆压着——经验性记忆压过指令,这是模型的通病,不是它不听话。

我第一反应是再改提示词:加粗、置顶、说三遍。改到一半想明白了——没用。提示词是读取时的对抗,记忆是存储时的污染;源头还在写,读取侧怎么对冲都是打地鼠。最后落了四层:

  • 写入侧:记忆分层纪律——环境与工具的临时状态(工具报错、宿主故障、瞬时网络异常)只许写 local 层(会话结束即弃),严禁写持久层。环境故障会被平台修复,持久化的环境状态必然过期,过期就是毒;
  • 存储侧:五条毒记忆软删(留删除时间戳,不物理删,保审计口径);
  • 读取侧:系统提示权威声明——Task 工具由平台接管、始终可用,历史记忆里的"宿主故障"均为过时记录,禁止据此绕开;
  • 检测侧:清毒纪律入册,部署时执行。

        0fe4eda fix(prompt): 记忆分层纪律——环境状态只进 local 层,堵毒记忆写入源头
bdba07f fix(prompt): 计划工具平台接管权威声明——对冲旧记忆毒知识
      
出处:v3 git log(2026-10-14,两 commit body 含五条毒记忆实证)。

这个坑的教训比事故本身值钱:Agent 的记忆是资产,也是污染面。它会学习是好事,但它学到的东西里有一类是"对当时环境的正确观察"——这类知识的有效期跟环境绑定,环境一变就成了毒。你在修机器,还得管它的记忆卫生。

僵尸 run 与死循环:三种死法

run 的死亡形态,实战里集齐了三种。

第一种,"没声了"。线程退出了,没有任何终局事件,界面停在中途,刷新也没用。用户能发现的死亡不是最糟的,最糟的是死亡本身不可观测。

第二种,僵尸。交战中断后 run 的注册表状态还是 running,平台重启,它们变成僵尸——状态在内存里,重启即丢,沙箱容器会话也跟着消失。

第三种,声明死循环。Agent 想结束任务,调 task_complete——引导文件里写的是裸名,调不通;它换成纯文本说"我做完了",纯文本轮没有出口判定,对话继续;它再说一遍"我做完了"……实证现场:48 轮对话。

三种死法的修复各自落位:中断路径收尸 + 启动时扫尸(重启后把孤儿 run 状态收敛);收尸必补终局事件(界面刷新就能看到真实终态);run 线程退出必查终局,没有终局的退出本身就是审计事件;纯文本轮补出口、修正引导。


        8b1adba fix(runtime): 僵尸 run 双兜底——中断收尸 + 启动扫尸
2220b68 fix(runtime): 事件流闭合——收尸必补终局事件,根治刷新无效的死亡定格
5bef90c fix(runtime): 静默退出审计——run 线程退出必查终局,消灭「没声了」类死亡
4319c7f 根治完成声明死循环(conv=48 实证:task_complete 裸名引导调不通 + 纯文本轮无出口)
      
出处:v3 git log(2026-10-14)。

死循环这条的教训单独说一句:Agent 的"完成"是个协议,不是一句话。它说"做完了"不算完,得有机器认的出口——而出口本身也是会坏的,坏了的表现形式是无限礼貌地重复告别。

清单:其余的坑不展开,但都在案

三天 40 多个 commit,拣有普适性的列几条:

坑一句话
令做 pwn,它自作主张刷 crypto计划里声明了题型,执行时换了顺手的——计划声明题型强校验,声明什么就必须执行什么
模型全程英文输出用户看不懂自己的 Agent 在说什么——中文输出硬规则进系统提示
时间显示偏早 8 小时 ×3 处三处绕过集中解析、各算各的时区——收敛到单点
重命名输入框输一个字母被全选覆盖focus+select 放在 nextTick 里,手快的用户抢在 select 前敲了字——改同步执行,竞态窗口消除
问答被扩张为执行用户问个问题,Agent 当成任务开干——白名单制任务边界
模型幻觉弃枪它"以为"预算不够提前放弃——系统提示注入战役预算真数
出处:v3 git log(2026-10-12~14 各 commit);内部《实战反馈修复批次技术总结报告》(2026-10-13)。

选题边界那条多说半句:它和 v2 的越界是同一个思路——"想做什么"和"被允许做什么"之间必须有机器执法的缝,提示词糊不上。

保命钱:两段熔断

预算的故事值得单独讲,因为它是"代价"最直观的样本。

单轮预算熔断上线后,实战里发现一个尴尬:熔断掐死的常常是收尾。一个 run 的预算烧到线,熔断——但收尾动作(写记忆、沉淀经验卡、生成报告、声明完成)也要花钱,而且这些动作恰恰发生在最贵的总结阶段之后。掐在线上,Agent 死在"马上就能写遗书"的地方,前面烧的钱等于白扔。

方案是两段熔断:进攻段给预算的 85%,烧到线熔断;熔断后开收尾段,给剩下 15%——保命钱。收尾段里攻击类工具一律拒绝(审计留痕),只准收尾四件套:写记忆、写经验卡、生成报告、声明完成。


        进攻段 85% ——烧到线——→ 熔断报错 ——一键收尾——→ 收尾段 15%(攻击工具全禁)
      

定价侧还有个意外发现:Agent 运行时对兼容端点上的第三方模型(非原生模型 ID),按最高档牌价计价。实测一个 run:输入 121K + 缓存读 546K + 输出 3.3K,按那个档估 3.09 美元,恰好压过预算 3 美元的 85% 线(2.55)触发熔断——按便宜档算只有 0.57,解释不通。所以预警估算按保守档(宁可早提醒),精确熔断以实测为准;显示层加了折算系数,日后换端点只改一处常量。这个发现本身写进了回归测试,成了实证锚点。

出处:内部《实战反馈修复批次技术总结报告》第九章(run 66 实证,含 $3.09 > $2.55 回归锚点)。

断头管线:功能存在 ≠ 功能生效

最后一批坑最扎心:三条功能面全部存在、全部完好——但从未被触发过。

杀伤链泳道恒空、审计链"链完整(0 条)"、知识三库全 0。我第一反应是前端 bug。查完发现前端忠实呈现了一个从未发生过的动作:规划生成器全仓零调用方,没有任何代码路径会调它;审计链只接了审批和熔断两条路径,日常主路径(边界固化、状态变更)压根不落链;知识三库的写入工具功能齐全,但系统提示从没引导 Agent 用它——Agent 边做边只写记忆和经验卡,结构化战果对平台不可见。

另一个同类:证据包捕获函数里有个 except Exception: pass,失败被静默吞掉。实战 10 个 run 全部走完了终局链(沉淀出的 157 张经验卡覆盖全部 run),但证据包一张没有。失败原因不可考——因为失败本身无日志。修复不是"修好捕获",是让失败可观测:吞异常处补 warning、堆栈、上下文。吞掉的语义保留(捕获失败绝不影响 run 收敛),但从无声变有声。

这两类问题的共性比单个 bug 重要:机制存在 ≠ 机制生效。功能写了、测试绿了、页面在,没有任何东西触发它,它就等于不存在。而"没被触发"不报错,它是静默的。实战是唯一的照妖镜——这也是"信任层必须装进真实平台"的另一个理由:在测试环境里,断头管线和接通的管线长得一模一样。

这套方案的失效边界

单焦点是硬上限。整个平台是单交战、单焦点设计:一个作战室、一条对话流、一个沙箱。它把"一次交战"做成了闭环,代价是人只能盯一路。实战里我很快撞到这条墙:Agent 在跑,我得盯着;切出去干别的,回来它可能已经死循环了 48 轮。这不是实现缺陷,是架构定位——一个人盯一路,人的注意力就是吞吐量上限。

淬火修的是踩到的坑。40 多个修复 commit 是"发现了的"问题数,不是"存在过的"问题数。没踩到的坑照常存在,下一个死循环可能换了形态在路上。

门禁模式有个单点:定门禁的人。88,000 行我逐行读不过一成,验收全靠矩阵、回归、证明点三道机械门。这套体系防得住"实现骗验收",防不住"门禁本身定错"——矩阵漏登记一个功能面,零回退核对就漏一列。门禁的设计错误是我的人为错误,会被机械地放大。

治理的数字是测试口径。87 例越界拦截来自测试集实测;实战期间没有真实越界事件被拦下——这既说明 Agent 守规矩,也意味着执法面没被真刀真枪地验过。拦得住测试里的越界,和拦得住实战中的越界,是两个命题。

系列导航(按已发列表滚动):

  • F0《我开源了一个能证明 AI 干过什么的工具》
  • 第 1 篇《一个周末,我让大模型帮我造出了第一个安全 Agent》
  • 第 2 篇《缰绳:一句提示词防不住一个 Agent》
  • 本篇:第 3 篇《身体:把信任层装进一个真实平台——88,000 行怎么验收》

项目入口:GitHub TLotU-lab/cyberseccat-fence——v2 信任层的公开工件仓(授权合同模型、87+25 测试集、M3/M4 门记录)。v3 平台本体处于开源就绪态、尚未公开,本篇 v3 数字待开仓后可对账。

下一篇:事故单元。机制存在不等于机制生效——三起真实事故,三套写过了、测过了的机制,在真实路径上接到的值是错的。授权额度凭空变无限、七分钟零 token 僵死、48 轮停不下来——同一套形状,三种死法。

The text above was extracted automatically from the source page and may miss images, tables or code blocks. The original is authoritative.

Published by FreeBuf
This site only summarises and aggregates; copyright stays with the original author. Headlines and summaries follow the source — where they differ, the original wins.
Read the original ↗