文章目录
-
- 前言
- 一、先把三个词掰扯明白
-
- [1. 凭据(credential)](#1. 凭据(credential))
- [2. Secret Reference(秘密引用)](#2. Secret Reference(秘密引用))
- [3. 金丝雀(canary)](#3. 金丝雀(canary))
- 二、设计:先画边界表,再写代码
- 三、落地:九个阶段,一个都不能跳
-
- [1. Phase 0 · 基线与表征测试](#1. Phase 0 · 基线与表征测试)
- [2. Phase 1 · Secret Reference 与加密](#2. Phase 1 · Secret Reference 与加密)
- [3. Phase 2--3 · 迁移与存储](#3. Phase 2–3 · 迁移与存储)
- [4. Phase 4 · 信封与授权门](#4. Phase 4 · 信封与授权门)
- [5. Phase 5 · 解密与出口](#5. Phase 5 · 解密与出口)
- [6. Phase 6--8 · 审计适配、端到端测试、全仓回归](#6. Phase 6–8 · 审计适配、端到端测试、全仓回归)
- [7. 组织设计:8 个 Agent,1 个主控](#7. 组织设计:8 个 Agent,1 个主控)
- 四、收口战役:三件比代码本身更值钱的事
-
- [1. 事件一:P0 级漏洞------RLS 旁路](#1. 事件一:P0 级漏洞——RLS 旁路)
- [2. 事件二:结果文件里的矛盾被揪出](#2. 事件二:结果文件里的矛盾被揪出)
- [3. 事件三:双独立安全审计](#3. 事件三:双独立安全审计)
- 五、现状:离生产只差一道"人工"门
- 小结
P.S. 无意间发现了一个巨牛的人工智能教程,非常通俗易懂,对AI感兴趣的朋友强烈推荐去看看, 传送门https://blog.csdn.net/H1727548
前言
现在做 Agent 的人,十有八九都被 API key 折磨过。你高高兴兴给 Agent 接了个外部服务,转头它就把你的 key 原封不动复述进了对话里。那一刻你不是在写代码,你是在养一只会背密码的鹦鹉。
后来我盯上一套刚落地的 Secret / Credential Runtime,专门治这种病。核心诉求一句话:Agent 可以带着凭据去调用外部服务,但凭据的明文,模型看不到、日志看不到、数据库看不到,连它亲妈都看不到。
状态: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);
生产部署验收是唯一剩余人工门,生产启用继续禁止。)
今天我在仓库里把它的三层测试真跑了一遍:领域层的拒绝矩阵,42 种"看起来差不多"的变体全部被拒 ------大写 scheme、大写 UUID、带 port、藏 userinfo、塞 query、无连字符,每个变体一个子测试;应用层信封剥离 6 测全绿;执行层 e2e 4 测全绿,其中最讲究的叫 RealResolverCanaryHeaderExactlyOnce:用真实解密链让金丝雀明文流到 mock 外部服务,断言 header 里恰好出现一次,所有内部出口零次。收口文件里那个最终数字也直白得可爱:plaintext_leak_total: 0。
顺便交代个反面教材:我本想在 /tmp 写独立 demo 调解析器,被 Go 的 internal 包规则拦住了------仓库刻意不对外暴露内部包,这本身就是一道安全边界。所以本篇实测全是跑仓库自带测试,这是诚实的选择,不是妥协的借口。行,说人话开整。
一、先把三个词掰扯明白
1. 凭据(credential)
凭据就是让外部服务认你身份的暗号:API key、访问令牌之类。它跟普通配置唯一的区别是:普通配置泄漏最多算社死,凭据泄漏直接算公司没。
泄漏渠道多到你不敢数:模型把它复述进对话、写进日志、存进数据库、被工具结果带回来、被 trace 顺手采走......这套运行时要做的,是把这些渠道全部焊死。不是堵一条,是全堵。堵一条叫打补丁,全堵才叫安全。
2. Secret Reference(秘密引用)
明文不能到处跑,那就给它发个代号。格式定死:
secret://vault/{小写UUID}
注意这个 UUID 是随机标识,不含任何信息量------你猜到天荒地老,也拿不到明文。设计原则就一句话:模型和一切记录里只允许出现代号,明文只允许出现在三个受控边界(写入加密时、解密回调内、发往外部服务的 header 里)。
说人话就是:明文这辈子只出三次门,比社恐还克制。社恐出门还要纠结半小时,明文出门是被人拿枪顶着的。
3. 金丝雀(canary)
矿工下井前带只鸟,鸟死了说明有毒气。我们这儿更狠,我们带的鸟会数数。
测试里把一个已知的假明文当真凭据跑完整链路,然后数它出现的次数:模型上下文 0 次、消息 0 次、日志 0 次、数据库 0 次、mock 外部服务的 header 恰好 1 次。次数对上了,才证明管道没漏。
别笑,这个"数鸟"方案,比市面上 90% 的所谓密钥管理方案都靠谱。人家是"我们很安全,请相信我们",我们是"我们很安全,请数数"。数得出来的安全,才叫安全。
二、设计:先画边界表,再写代码
1. 一张"明文边界表"定了全局
这套设计最值得抄的不是代码,是一张明文边界表:把系统里每个位置逐一行刑------哪里允许代号、哪里连代号都别想、哪里明文可以短暂现身。
具体规则就三条主线:
(1)模型侧
LLM 上下文、提示词、Agent 状态、工具调用参数、对话记忆摘要、工具结果、错误信息、SSE 流------代号随便放,明文想都别想。模型是泄密重灾区,它一开口,你的 key 就成了段子素材。
(2)记录侧
日志、追踪、审计、事件表------同上,只有代号。数据库更狠,连代号都不存,只存加密密文。管理 API 要啥啥没有,只有元数据,比渣男的前任还神秘。
(3)受控出口
受控写入→加密、解密回调、header 注入------明文可以来,但限时三秒,办完就走。普通工具业务逻辑?禁止。外部服务?那必须允许,不然这密钥是拿来镇宅的吗。
这张表看着朴素,执行力全靠十条安全不变量(SEC-01 到 SEC-10),每条都配了机械证明方式。比如 SEC-01"LLM 上下文出现次数为 0",证明方式是 FakeLLM 捕获请求后精确扫描。不变量写成可执行的断言,而不是写成愿望------这是整个计划最硬的一条方法论。愿望这东西,写代码的人谁没几个,最后全变成了事故。
2. 主落点为什么选 Tool Runtime
候选有四个:Agent 层(太早,会污染模型)、Tool Gate(管不住真正的网络发送)、HTTP 层(完不成授权)、独立凭据微服务(MVP 就引入跨进程明文,得不偿失)。
最终答案是 Tool Runtime ------hybridToolBroker.Invoke 这一层。为什么?因为它既是所有出站请求的必经之路,又能做授权判定(主体/工具/秘密三方匹配)。改动最小、控制最全,属于"你全都要,但你不用多付钱"。
三、落地:九个阶段,一个都不能跳
计划把落地切成 9 个阶段,每阶段带验收 ID 和 STOP 条件。下面是我实际走过的台阶,每一步的产物在仓库里都能查到。
1. Phase 0 · 基线与表征测试
先不写新功能,把现有行为用测试锁死:"无凭据的调用走原路径,逐字节不变"。这叫表征测试(characterization),是后面一切改动的安全网。改完代码跑一下它,绿灯=旧世界没被动过。
听起来平平无奇?这是全项目性价比最高的一步。没有它,后面每次改动都是在悬崖边蹦迪,还觉得蹦得挺好看。
2. Phase 1 · Secret Reference 与加密
写解析器,就是我跑的那个 42 拒绝矩阵。有个细节值得单拎出来说:scheme 检查必须在标准库 url.Parse 之前做 ,因为 url.Parse 会把 SECRET:// 静默小写成合法 scheme,绕过你的大小写校验。
go
// 别学我,先被坑过
u, _ := url.Parse("SECRET://vault/xxx")
// url.Parse 会把 scheme 静默小写:SECRET 变 secret,
// 你的大写校验从此形同虚设
if !strings.EqualFold(u.Scheme, "secret") {
return errors.New("非法 scheme")
}
这个坑特别阴。你写好了大写校验,url.Parse 默默把它小写了,然后你的校验代码笑呵呵地说"合法"。就像你妈进你房间不敲门,还顺手把你的空调关了,等你反应过来,感冒已经安排上了。
加密复用现有的 AES-256-GCM,只加了个 AAD 绑定:把租户/秘密/版本三要素绑进密文的认证范围。密文被挪到别的租户或别的版本下,直接解不开。相当于钥匙上刻了房号,串门也开不了锁。
3. Phase 2--3 · 迁移与存储
五张新表(秘密、版本、授权、出口策略......)走 000211 迁移,全部带行级安全(RLS);存储层加密落库,管理 API 只回元数据------明文?没有。密文?也没有。你问啥都只有元数据,比渣男的前任还神秘。
4. Phase 4 · 信封与授权门
模型发起工具调用时多带一个保留字段 credentials,Gate 在业务校验之前把它剥掉------下游工具只看到业务参数,密钥像被信封包着,拆都不让你拆。
授权判定是一条 13 个 AND 的链:租户精确匹配、授权在有效期、主体匹配、工具名匹配、秘密状态正常、版本存在、出口策略激活、host/path/method 三元组精确命中......任何一环失败即拒绝。注意这个细节:任何一环查询出错,也拒绝。fail-closed:宁可误杀一千,不可放过一个。像我查对象手机,查不到也当有事处理,安全永远优先于体面。
5. Phase 5 · 解密与出口
真正发请求的地方,顺序是铁的:出口预检 → 参数复核 → 注入方式复核 → 先写"使用意图"审计(写失败就不解密、不发网) → 才在解密回调里注入 header 发出。
响应只回状态码------远端返回的 body/header 一律不进工具结果。为什么?防"远端回显"把你的 key 念回来。你打个电话,对方一开口念你密码,你还没反应过来,话已经传出去了。所以干脆不让你说话,只让你"嗯"一声。
重定向全拒、私网 IP 全拒。为什么拒私网?因为你的 key 万一被拐到内网溜一圈,回来就不是你的 key 了。跟猫似的,放出去浪一圈,回来就不认识你了。
6. Phase 6--8 · 审计适配、端到端测试、全仓回归
包括金丝雀链路、泄漏矩阵、RLS 越权测试、并发测试,最后全仓 build/lint/DDD/测试/race 全绿。这块没什么好讲的,属于工程领域的"把地扫干净"。
但你知道吗,扫干净地的人,通常也是第一个发现角落有屎的人。扫地是手段,找屎才是目的。
7. 组织设计:8 个 Agent,1 个主控
计划还规定了最多 8 个并行 Agent、1 个主控,主控是唯一的提交者和验收者;每个工人只改自己独占的路径;任何安全门出现金丝雀泄漏立即全局叫停。
把"多 Agent 协作"本身当成需要设计的系统------多 Agent 干活最怕的不是活多,是活乱。8 个人同时改同一个文件,那不叫协作,那叫密室逃脱,而且逃脱的是你的代码。
四、收口战役:三件比代码本身更值钱的事
按计划写完只是及格线。收口阶段发生了三件事,每一件都比代码本身更有参考价值。
1. 事件一:P0 级漏洞------RLS 旁路
000211 的五张表用了"平台管理员可旁路"的策略,判断条件是一个数据库会话变量(GUC)------但 app_user 自己就能设置这个变量。
翻译成人话:普通人给自己贴个"我是管理员"的标签,就能跨租户读写删。这不是漏洞,这是给门卫发了一把万能钥匙,钥匙上还写着"我是门卫"。
机械负例测试在主分支上跑出 28/40 格红------40 个越权尝试成功 28 个。这个数字你敢信?这不是漏洞,这是开卷考试,而且及格线 70 分,你考了 70。
修复(迁移 000214)直接删掉五张表的旁路策略,负例重跑 44 PASS / 0 FAIL ,且 down 迁移能精确还原、往返闭环。注意:这个漏洞没有任何已知利用,是被自己人用负例测试主动挖出来的。自己给自己扫雷,扫完还留了张"此处有雷"的纸条。
2. 事件二:结果文件里的矛盾被揪出
收口证据文件曾同时写着 all_pass: true 和某项 PENDING,人工门状态还停留在旧时点------多轮追加字段没回填早期状态所致。
处理方式是修正矛盾并以追加更正块留痕,不静默重写。证据文件自己必须自洽,证明体系本身也要被审计------这是"审计门的门",同一套纪律。
这事特别丢人,但也特别提气:写报告的和写代码的是同一拨人,结果报告自己先打起来了。好在发现得早------如果这份文件带着矛盾进生产,审计的时候就不是丢人,是丢人 plus。
3. 事件三:双独立安全审计
收口后跑了两轮独立评估(实现质量 + 终态治理),结果 0 严重 / 0 高危。比我的体检报告还干净。
2 个中危都是 fail-closed 方向(配置陷阱而非越权),连同 5 个低危一起登记进遗留清单等批次修复。最有意思的一条 MEDIUM:出口路径前缀匹配的归一化逻辑在 Go 侧和 SQL 侧不一致,管理员配 "/" 或 "/api/" 时规则会静默失效------方向是不放行(安全),但属于坑管理员的配置陷阱。
审计报告敢把"我们的两处实现不一致"写进表格,这比 0 严重更让人放心。敢自曝家丑的团队,通常家里真没丑事。
五、现状:离生产只差一道"人工"门
- 代码 :合并链
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 包)我未复跑------那是收口战役的证据(连续两次全绿),我引用而不冒领。生产部署验收未发生,生产启用仍是禁止状态。
小结
1. 先画边界表,再写代码。"哪个位置允许明文"一行一行定死,每行配机械证明------设计阶段的这张表,比实现阶段的任何技巧都值钱。42 个拒绝变体、10 条不变量、金丝雀计数,全是把"禁止"翻译成"可断言"的动作。
**2. MVP 的克制是安全设计的一部分。**只支持 HTTPS 443、只支持一个凭据、重定向全拒、远端 body 一律不回、MCP 一律不接------每一条"不做"都消掉一类攻击面。计划里"禁止范围"一节与"交付范围"同等醒目,拒绝诱惑也是一种能力。
**3. 落地 ≠ 写完代码。**这份"落地"包含:表征测试锁旧世界 → 负例测试挖出自己 28/40 的 RLS 旁路 → 修复并往返验证 → 证据文件矛盾被自查修正 → 双独立审计 + 遗留登记 → 人工门清单。
最后那道"生产仍禁用"的门保持关着,本身就是这套安全工程的收笔。门关着,才是安全的。门开着,那是事故。
P.S. 无意间发现了一个巨牛的人工智能教程,非常通俗易懂,对AI感兴趣的朋友强烈推荐去看看,传送门https://blog.csdn.net/H1727548