拆解对象:DeepFlux 刚完成的 Secret / Credential Runtime------一套让 Agent 能携带凭据调用外部服务、但凭据明文绝不进入模型/消息/日志/数据库的运行时。素材是一份 1652 行的实施计划和它背后已全部落地的代码;本篇讲两件事:这套东西是怎么设计的,以及"落地"两个字到底包含哪些步骤。
先交代真实状态。这套东西的计划文档头部写着一行状态:
bash
状态:IMPLEMENTED / LOCAL_MVP_PASS / LOCAL_COMPLETE_PRODUCTION_WAITING_HUMAN
(2026-09-17 收口更新:Phase 0--8 全部完成,本地自动化门禁全绿;
P0 漏洞 ADR-025 §10-1 已由迁移 000214 收口;ADR-025 已 ACCEPTED;
push 已完成(82b2e3f6 已至 origin/main 与 github/main);
生产部署验收是唯一剩余人工门,生产启用继续禁止。)
今天我在仓库里实跑了它的三层测试:领域层 Secret Reference 解析的拒绝矩阵------42 种"看起来差不多"的变体全部被拒 (大写 scheme、大写 UUID、带 port、藏 userinfo、塞 query、无连字符......每个变体一个子测试);应用层 信封剥离 6 测全绿(含"无凭据调用逐字节透传"的零回归锁定);执行层 e2e 4 测全绿------其中最讲究的一个叫 RealResolverCanaryHeaderExactlyOnce:用真实解密链让金丝雀明文流到 mock 外部服务,断言header 里恰好出现一次 、而所有内部出口零次 。收口证据文件里那个最终数字也很直白:plaintext_leak_total: 0。
一个反面交代:我本想在 /tmp 写独立 demo 调用解析器,被 Go 的 internal 包规则拦住(仓库刻意不对外暴露内部包------这本身就是安全边界)。所以本篇的实测全部是跑仓库自带测试,这是诚实的选择,不是妥协的借口。
一、先弄懂三个词
凭据(credential) :让外部服务认你身份的秘密------API key、访问令牌。它和普通配置的区别只有一个:泄漏即事故 。泄漏渠道远比想象多:模型把它复述进对话、写进日志、进数据库、被工具结果带回、被 trace 采集......这套运行时要堵的是全部渠道。
Secret Reference(秘密引用) :明文的"代号"。格式定死为 secret://vault/{小写UUID}------注意这个 UUID 是随机标识 ,不含任何信息量,猜到它也拿不到明文。设计原则一句话:模型和一切记录里只允许出现代号,明文只允许出现在三个受控边界(写入加密时、解密回调内、发往外部服务的 header 里)。
金丝雀(canary) :矿工带进矿井的鸟。测试里把一个已知的假明文当真凭据跑完整链路,然后数它出现的次数------模型上下文 0 次、消息 0 次、日志 0 次、数据库 0 次、mock 外部服务的 header 恰好 1 次。出现次数对了,才证明管道没漏。
二、设计:一张"明文边界表"定了全局
这套设计最值得抄的不是代码,是那张明文边界表------它出现在计划 §1.3,把系统里每个位置逐一行刑:
| 位置 | Secret 代号 | Secret 明文 |
|---|---|---|
| LLM 上下文 / 提示词 | 允许 | 禁止 |
| Agent 状态 / 工具调用参数 | 允许 | 禁止 |
| 对话 / 记忆 / 摘要 | 允许 | 禁止 |
| 工具结果 / 错误 / SSE 流 | 允许 | 禁止 |
| 日志 / 追踪 / 审计 / 事件表 | 允许 | 禁止 |
| 数据库密文列 | 不需要 | 禁止(只存加密密文) |
| 受控写入→加密 / 解密回调 / header 注入 | 允许 | 允许(限定生命周期) |
| 普通工具业务逻辑 | 允许 | 禁止 |
| 外部服务 | 不要求 | 允许(协议所需) |
"允许代号、禁止明文"贯穿每一行。这张表的执行力来自十条安全不变量(SEC-01 到 SEC-10),每条都配了机械证明方式 ------比如 SEC-01"LLM 上下文出现次数为 0"的证明是"FakeLLM 捕获请求后精确扫描"。不变量写成可执行的断言,而不是写成愿望------这是整个计划最硬的一条方法论。
第二个关键决策是主落点选在哪 。候选有四个:Agent 层(太早,会污染模型)、Tool Gate(控制不了真正的网络发送)、HTTP 层(完不成授权)、独立凭据微服务(MVP 就引入跨进程明文,得不偿失)。最终答案是 Tool Runtime ------hybridToolBroker.Invoke 这一层既能做授权判定(主体/工具/秘密三方匹配),又是所有出站请求的必经之路。改动最小、控制最全。
三、落地步骤:Phase 0--8,一个都不能跳
计划把落地切成 9 个阶段,每阶段带验收 ID 和 STOP 条件。这里给的是实际走过的台阶(每一步的产物都在仓库里可查):
Phase 0 · 基线与表征测试 。先不写新功能,把现有行为用测试锁死------"无凭据的调用走原路径,逐字节不变"。这条表征测试(characterization)是后面一切改动的安全网:改完之后跑它,绿灯证明旧世界没被动过。
Phase 1 · Secret Reference 与加密 。写解析器(就是我跑的那个 42 拒绝矩阵)和加密扩展。解析器有个值得单说的细节:scheme 检查必须在标准库 url.Parse 之前做 ------因为 url.Parse 会把 SECRET:// 静默小写化成合法 scheme,绕过大小写校验。加密复用现有的 AES-256-GCM,只加了一个 AAD 绑定(把租户/秘密/版本三要素绑进密文的认证范围------密文被挪到别的租户或别的版本下解不开)。
Phase 2--3 · 迁移与存储 。五张新表(秘密、版本、授权、出口策略......)走 000211 迁移,全部带行级安全(RLS);存储层加密落库,管理 API 只回元数据------永不返回明文或密文。
Phase 4 · 信封与授权门 。模型发起工具调用时多带一个保留字段 credentials,Gate 在业务校验之前 把它剥掉------下游工具只看到业务参数。授权判定是一条 13 个 AND 的链:租户精确匹配、授权在有效期、主体匹配、工具名匹配、秘密状态正常、版本存在、出口策略激活、host/path/method 三元组精确命中------任何一环失败即拒绝,任何一环查询出错也拒绝(fail-closed:宁可误杀,不可放行)。
Phase 5 · 解密与出口 。真正发请求的地方,顺序是铁的:出口预检 → 参数复核 → 注入方式复核 → 先写"使用意图"审计(写失败就不解密、不发网) → 才在解密回调里注入 header 发出。响应只回状态码------远端返回的 body/header 一律不进工具结果(防"远端回显"把你自己的 key 念回来)。重定向全拒、私网 IP 全拒。
Phase 6--8 · 审计适配、端到端测试、全仓回归。包括那条金丝雀链路、泄漏矩阵、RLS 越权测试、并发测试,最后全仓 build/lint/DDD/测试/race 全绿。
九个阶段之外还有一层组织设计值得一提:计划规定了最多 8 个并行 Agent、1 个主控 ,主控是唯一的提交者和验收者;每个工人只改自己独占的路径;任何安全门出现金丝雀泄漏立即全局叫停。把"多 Agent 协作"本身当成需要设计的系统------这是这份计划的另一个样本价值。
四、收口战役:修漏洞,也修"报告里的矛盾"
按计划写完只是及格线。收口阶段发生了三件事,每一件都比代码本身更有参考价值。
事件一:P0 级漏洞------RLS 旁路 。000211 的五张表用了"平台管理员可旁路"的策略,判断条件是一个数据库会话变量(GUC)------但 app_user 自己就能设置这个变量 。也就是说:普通应用用户自设一个标志,就能跨租户读写删。机械负例测试在主分支上跑出 28/40 格红 ------40 个越权尝试成功 28 个。修复(迁移 000214)直接删掉五张表的旁路策略,负例重跑 44 PASS / 0 FAIL ,且 down 迁移能精确还原、往返闭环。注意细节:这个漏洞没有任何已知利用,是被自己人用负例测试主动挖出来的。
事件二:结果文件里的矛盾被揪出 。收口证据文件曾同时写着 all_pass: true 和某项 PENDING,人工门状态还停留在旧时点------多轮追加字段没回填早期状态所致。收口时的处理是修正矛盾并以追加更正块留痕,不静默重写 。证据文件自己必须自洽,这跟第 104 篇"审计门的门"是同一条纪律:证明体系本身也要被审计。
事件三:双独立安全审计 。收口后跑了两轮独立评估(实现质量 + 终态治理),结果 0 严重 / 0 高危 ;2 个中危都是 fail-closed 方向(配置陷阱而非越权),连同 5 个低危一起登记进遗留清单等批次修复------其中最有意思的一条 MEDIUM:出口路径前缀匹配的归一化逻辑在 Go 侧和 SQL 侧不一致 ,管理员配 "/" 或 "/api/" 时规则会静默失效------方向是不放行(安全),但属于坑管理员的配置陷阱。审计报告敢把"我们的两处实现不一致"写进表格,这比 0 严重更让人放心。
五、现状:离生产只差一道人工门
准确状态(计划 §13.5,含最后核对):
- 代码 :合并链
ed4ae0d2已推送,82b2e3f6在 origin/main 与 github/main;当前 HEAD 门禁全量复跑绿(build/lint/DDD/单测/集成/开放 API 校验/vet/eval 快照)。 - 架构决策 ADR-025 :负责人 2026-09-17 验收,ACCEPTED。
- 生产启用:NO-GO,等人工 。剩余三件事全是人的事:部署前安全验收记录、负责人单独授权、生产环境配置
DF_SECRET_KEY。设计上这个"没配置"状态是安全的------凭据能力整体禁用、fail-closed,不是事故态。 - 两项"工作树字面不绿"如实登记:inventory 有 2 个非本战役文件未登记(别的战役产物);共享工作树的 gitleaks 命中全部来自 gitignored 的本地文件------门禁口径已固定为干净树扫描(git archive HEAD = 0 findings),两种口径不混用。
如实分层:本篇实测覆盖领域层/应用层/执行层与 e2e 金丝雀(全部本机真跑);全量集成测试(257 包)我未复跑 ------那是收口战役的证据(连续两次全绿,记录在 .agent-runs/secret-credential-runtime/closure-current/FINAL/),我引用而不冒领。生产部署验收未发生,生产启用仍是禁止状态。
小结
- 先画边界表,再写代码。 "哪个位置允许明文"一行一行定死,每行配机械证明------设计阶段的这张表,比实现阶段的任何技巧都值钱。42 个拒绝变体、10 条不变量、金丝雀计数,全是把"禁止"翻译成"可断言"的动作。
- MVP 的克制是安全设计的一部分。 只支持 HTTPS 443、只支持一个凭据、重定向全拒、远端 body 一律不回、MCP 一律不接------每一条"不做"都消掉一类攻击面。计划里"禁止范围"一节与"交付范围"同等醒目。
- 落地 ≠ 写完代码。 这份计划的"落地"包含:表征测试锁旧世界 → 负例测试挖出自己 28/40 的 RLS 旁路 → 修复并往返验证 → 证据文件矛盾被自查修正 → 双独立审计 + 遗留登记 → 人工门清单。最后那道"生产仍禁用"的门保持关着,本身就是这套安全工程的收笔。