「跨组织 Agent 协作周」Day 5 · 收官篇。前四天我们拆了门票(全景)、名片(Agent Card 与发现层)、握手(委托链)、责任(审计与归责),今天把结论压成一份可执行的 checklist,并交付一个纯标准库的门检校验器
gate_check.py------输入一份对接档案 JSON,逐条机检,输出 PASS/FAIL。
TL;DR
- 收官清单四条主线:凭证不过手 / 防线在边界外 / 注册表当索引不当背书 / 出事时十分钟三问
- 本文含 12 条硬条款的完整定义 + 可运行代码(纯标准库,Python 3.x 直接跑)
- 每条条款都给了本周新闻里的现场注脚,不是纸上谈兵
一、为什么收官日给的是清单而不是新知识点
因为本周最后 72 小时的新闻,恰好把前四天的每一条判断都「现场验证」了一遍:
| 本周判断 | 现场注脚(来源见文末) |
|---|---|
| 凭证不过手 | 1Password for Agents:Agent 登录但密码在端点侧注入,Agent 拿到一次性授权(10-22) |
| 智能的供应与权力分离 | Nadella 发文 + Red Hat×NVIDIA OpenShell/Sentry:内核层划界、DPU 内存取证(10-19~20) |
| 注册表当索引不当背书 | RedMonk:MCP 注册表收录不审查、A2A 卡片签名可选(10-19);AWS:「Registry 提供连接元数据,不是认证材料」(10-20) |
| 治理意愿强、能力弱 | OutSystems×KPMG 调研:82% 自信 vs 26% 会解冲突,仅 5% 有自动化控制(10-22) |
反面样本也同场出现了:那起「$50 Hack」------一人一开源框架攻破 100+ 家组织,全战役 token 账单约 5,000 美元。防御侧输的不是模型,是边界:出站不设防、凭据混用、没有硬性消费上限。印度 CERT-In 的通报(10-15)给出第一号缓解要求:出站默认拒绝在网络层做,不靠提示词说明。
二、12 条硬条款
对接前(问清楚再开门)
- P1(HIGH) 对方给出
subject,指到自然人。仅凭 agent_id,出事后指不回人。 - P2(HIGH) 对方给出
policy_ref。策略没有版本号,等于无法回答「按哪一版规则放行的」。 - P3(HIGH) 凡要求把长期凭据交给对方 Agent 托管的条款,一律驳回。参照 1Password 的做法:短期、窄域、受信执行点代用。
- P4(MED) 总量账本在令牌体系之外。IETF 委托链草案自己承认:父令牌持 100 上限可铸任意多各持 100 的子令牌------单笔上限管不住扇出。
握手时(验证再放行)
- H1(HIGH) 委托链有可验证的衰减证明(
del_depth/par_hash一类)。act链只是家谱(RFC 8693 §4.1 明说嵌套 Actor 不参与访问控制),链深 ≥2 就没人能强制「子不宽于父」。 - H2(HIGH) 出站控制落在网络层。提示词说明不算控制。
- H3(MED) 卡片验签且公钥来源已知。签名只在事先已知对方公钥时才有意义------先安全换钥,再谈签名。
- H4(MED) 发现层收录仅作索引。能力与权限另行验证,参考 AWS 的治理设计:发布者与审批者 IAM 分离,DRAFT → APPROVED 全程可审计。
运行与出事时(日志能指回一个人)
- A1(HIGH) 审计写入路径与 Agent 执行身份分离。日志要防住它所记录的 Agent。
- A2(MED) 撤销事件记「验证结果」而非「发出指令」。父令牌撤销不级联子令牌是现实。
- A3(MED) 留存按「证据」而非「排障」设定:trace 可采样、可七天;审计不可采样、按年。
- A4(MED) 审计带第八列
initiated_by,区分「操作者行为」与「设计者行为」------出事时这两类的追问对象完全不同。
验收方法(从 Safeguard 的清单抄来的):把护栏逐个关掉,看沙箱还挡得住什么------那个缺口才是你的真实暴露面。
三、门检校验器:gate_check.py
纯标准库、无第三方依赖、可离线运行。以下代码在 Python 3.13.14 实测通过(文末附实测输出)。
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
gate_check.py ------ 跨组织 Agent 对接门检校验器(纯标准库,可离线运行)
用法:
python gate_check.py profile.json
cat profile.json | python gate_check.py
退出码:0 = 通过;1 = 存在 HIGH 缺项,或 MED 缺项 >= 2
"""
import json
import sys
def _nonempty(v):
return isinstance(v, str) and v.strip() != ""
# 每条: (编号, 分段, 严重级, 判定函数, fail提示)
CHECKS = [
# 对接前
("P1", "对接前", "HIGH", lambda p: _nonempty(p.get("subject")),
"subject 未指向自然人。仅凭 agent_id 无法在出事后指回到人"),
("P2", "对接前", "HIGH", lambda p: _nonempty(p.get("policy_ref")),
"policy_ref 缺失。策略没有版本号,等于无法回答'按哪一版规则放行'"),
("P3", "对接前", "HIGH", lambda p: p.get("credential_held_by") != "counterparty_agent",
"长期凭据由对方 Agent 托管。凭证不过手原则被违反,应改为短期窄域令牌或受信执行点代用"),
("P4", "对接前", "MED", lambda p: p.get("ledger_type") != "token_only",
"额度账本只存在于令牌内。单笔上限管不住扇出,总量账本必须在令牌体系之外"),
# 握手时
("H1", "握手时", "HIGH", lambda p: p.get("decay_proof") == "del_depth_par_hash",
"委托链无可验证的衰减证明。act 链只是家谱,无法强制'子不宽于父'"),
("H2", "握手时", "HIGH", lambda p: p.get("egress_control") == "network_layer",
"出站控制在提示词层。出站默认拒绝必须落在网络层,提示词说明不算控制"),
("H3", "握手时", "MED", lambda p: p.get("card_signature") == "jws_known_key",
"卡片未验签或验签密钥来源不明。签名只在事先已知对方公钥时才有意义"),
("H4", "握手时", "MED", lambda p: p.get("discovery_role") == "index_only",
"发现层收录被当作背书。注册表只担保名字归属,能力与权限必须另行验证"),
# 运行与出事时
("A1", "出事时", "HIGH", lambda p: p.get("audit_write_isolation") is True,
"审计写入路径与 Agent 执行身份未分离。日志要防住它所记录的 Agent"),
("A2", "出事时", "MED", lambda p: p.get("revocation_records") == "verification_result",
"撤销事件记录的是'发出指令'而非'验证结果'。父令牌撤销不级联子令牌"),
("A3", "出事时", "MED", lambda p: p.get("retention_policy") == "evidence",
"留存策略按排障而非证据设定。trace 可采样、可七天;审计不可采样、按年"),
("A4", "出事时", "MED", lambda p: p.get("initiated_by_enabled") is True,
"审计缺 initiated_by 列。操作者行为与设计者行为无法区分"),
]
def check(profile):
results = []
high = med = 0
for cid, phase, sev, ok, hint in CHECKS:
passed = ok(profile)
if not passed:
if sev == "HIGH":
high += 1
elif sev == "MED":
med += 1
results.append((cid, phase, sev, passed, hint))
return results, high, med
def render(results, high, med):
lines = []
phase = None
for cid, ph, sev, passed, hint in results:
if ph != phase:
lines.append(f"--- {ph} ---")
phase = ph
mark = "PASS" if passed else "FAIL"
lines.append(f"[{mark}] {cid} ({sev})" + ("" if passed else f" -- {hint}"))
verdict = "PASS" if (high == 0 and med < 2) else "FAIL"
lines.append(f"\n结论: {verdict} (HIGH 缺项: {high}, MED 缺项: {med})")
return "\n".join(lines), verdict
def main():
if len(sys.argv) > 1:
with open(sys.argv[1], encoding="utf-8") as f:
profile = json.load(f)
else:
profile = json.loads(sys.stdin.read())
results, high, med = check(profile)
text, verdict = render(results, high, med)
print(text)
sys.exit(0 if verdict == "PASS" else 1)
if __name__ == "__main__":
main()
档案示例(合规版)
json
{
"agent_name": "good-partner-agent",
"subject": "emp-0182:zhang.wei(法务合规负责人)",
"policy_ref": "agent-policy-v2.3.1",
"credential_held_by": "gateway",
"ledger_type": "external_ledger",
"decay_proof": "del_depth_par_hash",
"egress_control": "network_layer",
"card_signature": "jws_known_key",
"discovery_role": "index_only",
"audit_write_isolation": true,
"revocation_records": "verification_result",
"retention_policy": "evidence",
"initiated_by_enabled": true
}
实测输出(Python 3.13.14)
合规档案:
scss
--- 对接前 ---
[PASS] P1 (HIGH)
[PASS] P2 (HIGH)
...
--- 出事时 ---
[PASS] A4 (MED)
结论: PASS (HIGH 缺项: 0, MED 缺项: 0)
把 credential_held_by 改回 "counterparty_agent"、egress_control 改成 "prompt_only" 再跑:
scss
[FAIL] P3 (HIGH) -- 长期凭据由对方 Agent 托管。...
[FAIL] H2 (HIGH) -- 出站控制在提示词层。...
结论: FAIL (HIGH 缺项: 2, MED 缺项: 0)
退出码 1------可以直接挂进 CI,对接档案不过门检不进评审会。
四、设计取舍与局限
为什么做成「声明式档案」而不是连对方真实系统探测? 因为跨组织场景下你通常没有权限探测对方内部实现,评审阶段能拿到的是对方签署的对接档案。这份校验器检的是「档案与清单的一致性」,真实执行强度仍要在验收环境实测(scope escape test:逐个关护栏看沙箱)。
局限表(避免被误用):
| 局限 | 说明 |
|---|---|
| 不验证对方声明真实性 | 档案是自我声明,签名/公证是下一步,本工具不做 |
| 判定是枚举值匹配 | decay_proof 只认 "del_depth_par_hash" 这类约定值,自定义方案需自己扩展 lambda |
| 退出码策略保守 | 2 个 MED 也判 FAIL,团队可按风险容忍度调整阈值 |
| 不含网络/运行时探测 | 属于静态门检,动态验收需另建测试环境 |
五、踩坑记录
is True不是抠门。p.get("audit_write_isolation")若写if p.get(...),任何 truthy 值都过------对接档案里"true"字符串是 JSON 反序列化的常见坑,"false"字符串也是 truthy。显式is True配合上游 schema 校验。- 枚举值用
!=反向判。 P3 的条件是credential_held_by != "counterparty_agent",意思是「任何不等于违规值的取值都算过」------缺字段也过。这是有意的:字段级缺失应该由 P1/P2 这类 HIGH 项兜底,避免一份数据被重复扣分掩盖主因。 - 中文提示别写进异常。 fail 提示放数据结构里而不是
raise ValueError,是为了让 CI 采集 stdout 时格式稳定;退出码承载判定结果,文本只承载解释。 - 分段渲染先看
phase != phase。 最初版本用 enumerate 对齐分组索引,插入新检查项就错位;改成「与上一条 phase 不同则输出分组头」后增删项零维护。 - 退出码语义要写进 docstring。 0/1 的含义不写清楚,CI 接入的人一定会问第二遍。
六、收官:本周判断的最终形态
- 协议解决怎么传,不解决凭什么信 ------所以对接前先问
subject与policy_ref,不问「支不支持 A2A」。 - 防线在边界外------OpenShell/Sentry 把控制推进到内核与 DPU,1Password 把凭证收回到端点,方向一致:执行点离模型越远,防线越可信。
- 注册表当索引不当背书------发现与授权分层是 AWS 亲口确认的正确姿势。
- 责任要能十分钟三问------谁定的目标、谁给的额度、谁点的确认;答不出来,说明你治理的是系统不是人,而系统不会赔钱。
下周进入新主题:AI 应用的记忆与上下文持久化怎么工程化------本周讲的是「信任怎么建」,下周讲「状态怎么存」。欢迎在评论区留下你对接过的最离谱的第三方 Agent 档案。
来源:TechCrunch / The Verge(1Password for Agents,10-22);BidSense Daily AI Intelligence Report(10-23);cherumbu.com(Red Hat×NVIDIA GTC Berlin);dev.to「The $50 Hack」;safeguard.sh「Guardrails vs Sandboxes」;NHIMG Multi-Agent A2A Security Guide(10-20 更新);RedMonk(10-19);AWS ML Blog(10-20);OutSystems×KPMG×CIO Dive 调研;notesfromtheterminal.com(Visa 试点,10-22)。厂商自报数据均已标注口径。