aic-verifier — README(中文)中文

aic-verifier

身份说明"谁在调用";AIC 说明"这个智能体被允许做什么"——并且能离线证明。

License Go Version Go Reference Status IETF IETF

English · 中文


为什么

一个 API key 或一份 scope 列表说的是一般被允许什么。它说不出是哪个智能体在动手、 代表谁边界到底在哪,也留不下实际发生了什么。当调用方是一个可能被提示注入、 被重放、被层层转授的自主智能体时,"平台会校验令牌"是一句承诺,不是一份证明。

aic-verifier 把这份承诺变成一个执行点:一个小的 Go 库,包在你任意 HTTP 服务外面——或 以反向代理的方式挡在 API 前面——它对每个请求验证 AIC(Agent Identity Certificate), 并依据证书里携带的能力对精确操作做裁决。裁决是三值的(allow / allow_unresolved / deny),拒绝发生在你的 handler 运行之前,而每一次裁决都可以落成一条可复算、 可离线验证、绑定到授权主体的证据记录。

它是执行点,不是网关。 aic-verifiervarwof gateway 可嵌入的准入核心。它负责裁决与记录;路由、自带反代之外的各种转发、以及"执行",都属于调用方。 aic-exec 就是建立在它之上的命令执行边界。

它求值的是能力语言的 CLC-1.8 修订版,依赖 register v0.6.0(CLC 参考实现)与 types v0.6.0(AIC / AIC-JWT 结构)。


它做什么

领域 能力
凭据 带 AIC X.509 扩展的 mTLS 客户端证书,或 Authorization: Bearer <AIC-JWT>AuthMode = MTLSOnly / BearerOnly / MTLSOrBearer
裁决管线 证书有效性 → CRL/OCSP 吊销 → 角色 → AIC 裁决 → 能力 ∩ 主体授权 → 参数边界 → allow / allow_unresolved / deny
能力语言 CLC-1.8 具体操作、能力 ID 匹配、参数边界(max_rows、枚举……)、授权约束(CIDR、时间窗、并发)、残差义务
委托 DA / DA-v2 签名校验、委托链、EffectiveDelegationCapabilities、DA 新鲜度、主体密钥绑定、代表模式拒绝
集成方式 中间件(Handler / AuthMiddleware)包住你的 handler;反向代理(NewServer)注入 X-AIC-*;传输无关的 DecisionServerDecide、HTTP、gRPC、admin、health)
证据 每条裁决一条 DSSE 封装的 CLC 裁决记录,另加 admission 与 outcome 记录;FileSink/SlogSink密钥背书签名EvidenceConfig.Signer / SignKeyFile / SignRequireSignature 可 fail-closed);per-admission nonce;RATS §10 新鲜度;profile;requirement 绑定
证据验证 VerifyEvidenceDir(结构 + 签名 + 裁决↔结果链接,孤儿上报)、VerifyEvidenceEnvelopeVerifyFnFromKey / VerifyFnFromPublicKey(钉公钥)、RecordSigner.VerifyFn()LoadEvidenceRecord
证据导出 FileEvidenceExporterEvidenceBundle v0.1(manifest / operation / subject / authorization / decision / supervision / Merkle 审计链 / signatures),包本身可签名EvidenceBundle.Sign / VerifySignature),并可人类可读渲染RenderMarkdown / RenderCSV / RenderTextWriteRendered
挑战 CLC-CHALLENGE-v1:可补救的拒绝以 RFC 9457 application/problem+json + Retry-After 应答,并列出缺什么证据
审计 Merkle 链式 AuditLogger(可 TSA 签名)、VerifyAuditEntryFilterAuditFileArchiveAuditFile
监督 运行时人工审批(ApprovalRequester)、破玻璃的强制记录(OverrideRecorder)、RequireApproval 触发点、SupervisionPolicy、append-only SupervisionStore
策略 OU→角色 AuthorizationPolicy、PKCS#7 签名策略文件(SignPolicy/VerifySignedPolicy)、fail-closed 热重载(ReloadPolicy*、admin token)
插件 CapabilityPlugin、能力 RegistryConstraintEvaluatorParameterValidatorGeoResolver——按 Config 隔离(一个进程多个网关)
身份卫生 IdentityMode(除非你要求,后端看不到原始证书)、日志字段脱敏(序列号/邮箱/令牌/路径)
运维 /healthzHealthReportDecisionMetricsConfig.Validate()、TLS 助手、密码套件策略、OCSP stapling
载体 mcp/ 子包(受 AIC 保护的 MCP 服务)与 grpc/ 子包(AICDecisionService,codec aic-json-v1

完整 Config、记录形状与常量见 docs/reference.md


快速开始(两分钟)

四步,每步都很短。演示会自己生成 CA 和证书,不用先做配置。

1. 取代码并生成演示证书 —— 一个 CA,加一张服务端证书和一张带 AIC 扩展的 agent 证书:

git clone https://github.com/varwof/aic-verifier && cd aic-verifier
go run ./examples/mtls-backend/gen-cert -out ./demo-certs

2. 起受保护的服务 —— 它在 :9444 上终止 mTLS,把获准的请求转发到同一个进程起的 演示后端 :9081。让它在当前 shell 一直运行:

go run ./examples/mtls-backend --certs ./demo-certs

3. 以 agent 身份访问 —— 另开一个 shell,用 agent 证书调用:

curl -sS --cert demo-certs/client-cert.pem --key demo-certs/client-key.pem \
     --cacert demo-certs/ca-cert.pem https://localhost:9444/api
# {"backend":"real-api-mtls","identity":{"X-Forwarded-For":"127.0.0.1, 127.0.0.1"}}

4. 看一次拒绝 —— 请求一个 agent 无权做的操作,请求根本到不了后端:

curl -sS --cert demo-certs/client-cert.pem --key demo-certs/client-key.pem \
     --cacert demo-certs/ca-cert.pem https://localhost:9444/api/transfer
# {"code":"access_denied","message":"agent missing required capabilities"}

在第一个 shell 按 Ctrl-C 停掉演示。这次请求留下了什么、怎么复算,见 docs/quickstart.md(也包含不带客户端证书的调用会怎样)。


简易示例

包住你自己的 handler(中间件)

conf := &aicverifier.Config{
    CACertFile:           "certs/ca-cert.pem",
    AuthMode:             aicverifier.MTLSOnly,
    RequireAIC:           true,
    RequiredCapabilities: []string{"demo/example-v1:api:read"},
}

mux := http.NewServeMux()
mux.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
    ac := aicverifier.FromContext(r.Context()) // 智能体 id、主体、能力、裁决
    fmt.Fprintf(w, "hello %s\n", ac.AgentID)
})

handler, err := conf.Handler(mux) // 把整条管线套在 mux 外面
if err != nil {
    log.Fatal(err)
}
srv := &http.Server{
    Addr:      ":8443",
    Handler:   handler,
    TLSConfig: muxTLSConfig, // ClientAuth: tls.RequireAndVerifyClientCert,ClientCAs: 你的 CA
}
log.Fatal(srv.ListenAndServeTLS("certs/server-cert.pem", "certs/server-key.pem"))

反向代理,后端一行不改

target, _ := url.Parse("http://127.0.0.1:8080")
server, err := aicverifier.NewServer(conf, []aicverifier.Route{
    {Path: "/api", Target: target, RequiredCapabilities: []string{"demo/example-v1:api:read"}},
})
log.Fatal(server.ListenAndServe(":9444")) // 注入 X-AIC-* 身份头

在进程内授权一个操作

能力可以携带参数边界;申请超过授权范围是拒绝,不是警告。

dec, err := aicverifier.AuthorizeOperation(aic, pa,
    "std/database-v1:query:SELECT",
    map[string]any{"limit": 50})       // 授权写着 {"limit": 100} -> allow
switch dec.Verdict {
case semantics.VerdictAllow:
case semantics.VerdictAllowUR:          // 残差义务,不等于 allow
default:                                // VerdictDeny;dec.Reason 是规范原因码
}

只用 Bearer AIC-JWT

conf := &aicverifier.Config{
    JWTCAFile:  "certs/jwt-ca.pem",
    AuthMode:   aicverifier.BearerOnly,
    JWTIssuer:  "aic-verifier-example",     // 可选 iss 钉定
    JWTAudience: []string{"myapi"},         // 可选 aud 钉定
}

传输无关的裁决(HTTP + gRPC + 进程内结果一致)

core, _ := aicverifier.NewDecisionServer(conf)
ac, err := core.Decide(ctx, &aicverifier.RequestView{
    BearerToken:     token,
    TransportSecure: true,
})
// 同一个 Config -> HTTP、gRPC(grpc.NewDecisionService)或任何进程内载体裁决完全一致。

打开证据,然后离线验证

signer, _ := aicverifier.LoadRecordSignerFile("/etc/aic/evidence-key.pem", "pep-1")
conf.Evidence = &aicverifier.EvidenceConfig{
    Sink:             &aicverifier.FileSink{Dir: "/var/lib/aic/evidence", RecorderID: "pep-1"},
    Audience:         "https://gateway-a.example",
    TTL:              5 * time.Minute,     // RATS §10.1 新鲜度时钟
    Signer:           signer,              // 给每条记录做密钥背书(RSA/ECDSA/Ed25519)
    RequireSignature: true,                // 没有签名密钥就拒绝启动
    Strict:           true,                // sink 挂了就 fail closed
}
// ...之后,在热路径之外:
rep, err := aicverifier.VerifyEvidenceDir("/var/lib/aic/evidence",
    signer.VerifyFn())                     // 钉公钥;校验签名 + 链接

导出证据包、签名,再渲染给人看

exporter := &aicverifier.FileEvidenceExporter{
    AuditFile:       "/var/lib/aic/audit.jsonl",
    SupervisionFile: "/var/lib/aic/supervision.jsonl",
    EvidenceDir:     "/var/lib/aic/evidence",
    Signer:          signer,               // 给包本身签名,不只是记录
}
bundle, _ := exporter.Export(ctx, aicverifier.EvidenceQuery{AgentID: "agent-001"})
bundle.VerifySignature(signer.VerifyFn())  // 持有者可复核这次导出
bundle.WriteRendered("/tmp/evidence.md",   aicverifier.RenderMarkdown) // 或 RenderCSV / RenderText

用挑战应答一次可补救的拒绝

conf.Challenges = &aicverifier.ChallengeConfig{
    TTL:        2 * time.Minute,
    Audience:   "https://gateway-a.example",
    RetryAfter: 10 * time.Second,          // 变成 Retry-After 头
}
// 缺 §8.4 证据的拒绝返回 403 application/problem+json,正文是 CLC-CHALLENGE-v1:
// 缺什么,以及何时可以带着修正后的出示重试。

运行时人工审批 / 破玻璃

conf.SupervisionPolicy = aicverifier.SupervisionPolicy{RequireRuntimeApproval: true}
conf.ApprovalRequester = myApprover   // 若为 nil 且 RequireRuntimeApproval => 启动即报错
conf.RequireApproval = func(ac *aicverifier.AuthContext, r *http.Request) bool {
    return r.Method != http.MethodGet // 写操作路由给人工
}
// 破玻璃同样要求 recorder,所以"没记录的破玻璃"永远不会存在。

给 MCP 服务做门禁

reg, _ := mcp.LoadJSON(registryJSON)
h, _ := mcp.NewHandler(mcp.ServerConfig{ServerName: "aic-tools", Version: "0.1"},
    reg, map[string]mcp.ToolHandler{"echo": echoHandler})
// 用 conf.Handler(...)(或 conf.AuthMiddleware)包住 h 先按 AIC 准入;
// 每个工具调用可以通过 mcp.AuthContextFromToolContext 读到已验证身份。

更多可跑路径见 docs/examples.mdexamples/ 目录(mtls-backendbearer-jwt-backendmcp-servermcp-behind-proxysupervision-demoshowcaseinspect-recordsmoke-verify)。


裁决管线

每个请求走同一条管线,产出一个裁决:

  1. 凭据 —— mTLS 链校验,或 AIC-JWT 解析 + 验证(bearer 从不走明文:请求必须经 TLS)。
  2. 吊销 —— CRL 和/或 OCSP(可选;按 CRLCache / OCSPCache 配置)。
  3. 角色 —— OU→角色映射(AuthorizationPolicy)、RequireRoles、admin OU。
  4. AIC 裁决 —— 针对精确操作做能力 ∩ 主体授权,含参数边界。
  5. 约束 —— CIDR / 时间窗 / 并发(EnforceConstraints);执行器必须履行的残差义务保持 allow_unresolved
  6. 裁决 —— allow / allow_unresolved / deny,带稳定原因码。

拒绝是有类型的:*aicverifier.AuthError 带着 HTTP 状态、稳定原因码、这次拒绝产生的记录, 以及在"补交证据就能过"时附上的 RFC 9457 problem 文档与 CLC-CHALLENGE-v1 挑战。

allow_unresolved 不是 allow。已识别但未求值的约束绝不会被静默提升,它保持可见, 必须在执行边界被履行。


是证据,不是日志

只给一个"允许"没什么用,如果事后没人能查。配置了 Evidence 之后,SDK 会发出一条 CLC 裁决记录(输入在规范边界处冻结、对输入取摘要、给出裁决与稳定原因码),可选地签名 并包在 DSSE 信封里,通过 EvidenceSink 输出。到不了语言层的拒绝改发 admission 记录; 代理或中间件还可以把执行边界观察到的效果作为 outcome 记录回报。

  • 每个(授权来源,操作)一条记录。 一条裁决无法复算的记录在构造上不可能存在——它由同一 组授权重新算出。
  • 拒绝也被记录,所以"意图"和"动作"一样可审计。
  • 可离线复算。 register/cmd/record -verify 能对一条非持有者产出的记录重跑语言; VerifyEvidenceDir 还会把每条 outcome 绑到目录里真实存在的裁决上,并对孤儿上报, 绝不当作"同意"。
  • 想要密钥背书时就有。 EvidenceConfig.Sign 会给每条记录附上覆盖 PAE(payloadType, payload) 的 DSSE 签名;需要"这是哪个准入点发的"的部署拿得到, 不需要的部署不付任何字节(记录仍是内容可复算的,只是未签名)。

细节与固定形状的 profile 见 docs/evidence.md


合规契合

上面这些性质,直接对应监管写进条文里的硬要求。以下均为公开要求,对应到 SDK 实际做的事—— 不是认证声明。

要求(公开出处) aic-verifier 提供什么
强身份认证 / 唯一标识 —— HIPAA §164.312(d),(a)(2)(i);EO 14028 MFA;中国网安法 §24 真实身份 mTLS AIC 证书或 AIC-JWT,密钥绑定(SPKI / cnf
最小权限 / 细粒度授权 —— PIPL §51(四);HIPAA §164.312(a)(1);EO 14028 §4(i) 逐操作的能力 ∩ 主体授权,含参数边界——按动作裁决,不按会话
动作边界上的最小权限执行 —— NIST SP 800-207 在 handler 之前 fail-closed 拒绝;allow_unresolved 绝不静默 allow
不可篡改 / 可归因审计 —— SEC 17a-4(f);HIPAA §164.312(b);EU AI Act Art 12;中国网安法 §21 Merkle 链式审计 + DSSE 裁决记录,per-admission nonce,可选 TSA 时间戳
证据可离线核验 / 可追责 —— SEC 17a-4(f)(2)(iv),(f)(3)(v);EU AI Act Art 12(3)(d) 记录不依赖在线权威即可复算;钉公钥验证;可导出 EvidenceBundle
人工监督 / 审批 —— EU AI Act Art 14(4)(5),(26);PIPL §24;算法推荐规定 §7 RequireApprovalApprovalRequester,破玻璃强制 OverrideRecorder,可审计的监督事件
实时吊销 管线内的 CRL / OCSP;设计上鼓励短时凭据

它不做什么: 它约束不了绕过执行点的任何路径。证据证明的是准入裁决,不证明来源真实, 也不证明下游效果已成功(那是执行边界的职责)。它不是经认可的认证。


你实际会碰到的配置

字段 作用
CACertFile / JWTCAFile mTLS 客户端证书 / bearer 令牌的信任锚
AuthMode MTLSOnlyBearerOnlyMTLSOrBearer(默认)
RequireAIC 拒绝没有 AIC 扩展的证书
RequiredCapabilities / RequiredOperations 调用方必须满足的能力 ID,或带参数边界的具体操作
EnforceConstraints 求值授权约束(时间窗、CIDR、max_rows
AdmissionConfig CRL/OCSP、角色、SPIFFE、委托链、监控钩子
Evidence / EvidenceProfile / EvidenceRequirement 记录、记录的形状,以及充分性门槛
Challenge / ChallengeCarrier 可补救的拒绝是否带挑战,以及如何渲染
SupervisionPolicy / ApprovalRequester / OverrideRecorder 运行时审批与破玻璃
AuthorizationPolicy / Constraints / ParameterValidators 策略与注册表的按 Config 隔离
IdentityMode 向其后端披露多少已验证身份
ServerOptions / StreamBody / LogFile / Logger 代理服务器调优、body 流式、日志

完整字段、类型与默认值见 docs/reference.mddocs/api.mdconfig.example.json 与 JSON 接口面由 CI 保持一致。


文档地图

你想…… 从这里开始
两分钟试一下 quickstart.md
中间件反向代理之间做选择,并看代码 api.md · architecture.md
打开证据并理解一条记录的含义 evidence.md
了解每个 Config 字段、记录形状、常量与版本 reference.md
理解一次裁决为什么(不)可信 threat-model.md
上生产:TLS、密钥、监控、轮换 deployment.md
端到端跑示例 examples.md
比较 SDK 与完整网关,以及当前非目标 comparison.md

稳定性

v1.0 之前,接口面分成两类,方便嵌入方判断哪些可以放心依赖:

接口面 契约
冻结至 v1.0 —— ConfigHandler / AuthMiddlewareNewServerServer.{Listen,Serve,Addr,ListenAndServe,Close}NewDecisionServerDecisionServer.{Decide,Health,AdminHandler,ReloadPolicy,Close}AuthContextAuthErrorDecisionMetrics 只做增量:不改名、不删字段、不无公告地改默认值;二进制实际使用的 CLC 版本只在显式提升 CLCRevision 时才变动。
实验性 —— 插件/注册钩子(PluginRegistryCapabilityRegistryRegisterGeoResolver、参数校验器)、监督/证据接口(ApprovalRequesterOverrideRecorderEvidenceExporter),以及 examples/mcp/ 下的包 在周边格式稳定之前,可能在次版本中变化。

Config.CloseServer.Close / DecisionServer.Close 也会触达)会释放 Config 持有的后台资源——审计日志器、nonce 缓存清理协程、监督存储、SDK 日志文件。CRL 与 OCSP 刷新循环仍由调用方自行停止(CRLCache.StartStartOCSPStapling)。


依赖要求

要求 说明
Go 1.26 或更高 模块声明 go 1.26;不用 cgo
github.com/varwof/register v0.6.0 CLC 求值器与裁决记录格式
github.com/varwof/types v0.6.0 AIC / AIC-JWT 结构
github.com/varwof/pkcs7 v0.1.1 语义层分离签名校验
github.com/mark3labs/mcp-go v1.0.0 只有 import mcp 子包才需要

依赖随 go get 进来,仓库里没有 replace、也没有 vendor。SDK 本身不依赖任何外部服务: CRL/OCSP 响应端与 RFC 3161 时间戳服务都是可选的,配置了才会用。

下面的演示另需 curl(任何能出示客户端证书的客户端都行)和三个空闲端口: 9444(代理)、9081(演示后端,进程自己起)、以及后面用到的 9443(证据演示)。


相关仓库

仓库 角色
varwof/types 共享 Go 类型:AIC、AIC-JWT、能力
varwof/register 能力注册表、PKCS#7 签名、CLC 语义(本 SDK 用来求值的参考实现)
varwof/capability CLC 规范及其一致性语料
varwof/aic-agent 消费侧 SDK:铸造并携带本验证器所校验的凭据
varwof/aic-exec 基于本准入核心的、受 AIC 保护的命令执行器
varwof/gateway-core · varwof/gateway 完整网关;本 SDK 是其可嵌入的准入核心

许可证

Apache-2.0。见 LICENSE

另见 SECURITY.md(漏洞上报、安全承诺、加固清单)、 CONTRIBUTING.md(开发与检查清单)、 CHANGELOG.md