Signet:为AI代理工具调用构建可验证凭证层的实践指南
2026/5/8 15:25:19 网站建设 项目流程

1. 项目概述:为AI代理工具调用构建独立可验证的凭证层

在AI代理(Agent)日益成为自动化工作流核心的今天,一个长期被忽视的“信任鸿沟”正变得愈发尖锐。当你的AI代理在GitHub上创建了一个Issue、在数据库中删除了一行记录、通过API发送了一封邮件,甚至是在生产环境中执行了一个Bash命令时,你如何向第三方(比如审计员、客户、或是事故复盘时的你自己)证明,这确实是你的代理所为,且操作内容未被篡改?

传统的解决方案是日志。但日志本质上是一种“单方面声明”——它们由运行代理的平台生成和存储,可以被平台管理员修改或删除。当审计员问你要证据时,你只能指着云控制台说:“看,日志里是这么写的。” 这建立在一个脆弱的信任假设之上:你必须完全信任日志的提供者。在涉及合规、安全事件或跨组织协作的场景中,这种信任往往是不够的。

Signet项目正是为了解决这个问题而生。它不是一个日志系统,而是一个独立可验证的凭证层。它为每一次AI代理的工具调用生成一个密码学签名的“收据”。这张收据就像一张数字发票,包含了操作内容、执行者、时间戳,并由代理的私钥签名。任何人都可以在离线状态下,仅凭代理的公钥,验证这张收据的真实性和完整性。任何对收据内容的篡改——无论是修改参数、时间戳还是签名者信息——都会导致签名验证失败。Signet的核心价值在于,它将“发生了什么”从一个需要信任的声明,转变为一个可以独立验证的密码学证据。

2. 核心设计思路:从“信任声明”到“可验证证据”

Signet的设计哲学可以概括为“去中心化验证”和“最小化信任”。其架构围绕几个核心原则构建,这些原则决定了它为何如此工作,以及它如何融入现有的AI开发生态。

2.1 为什么选择密码学签名而非增强型日志?

许多团队的第一反应是:“我们可以把日志写进不可变的数据库,或者用区块链存证。” 这确实提高了篡改难度,但问题根源未变:证据的生成和存储依然依赖于一个中心化的、需要你信任的实体(数据库管理员、区块链节点运营商)。Signet采用了更根本的解决方案:在动作发生的那一刻,由动作发起者(代理)自己生成证据。

核心机制解析:Ed25519签名与哈希链Signet使用Ed25519椭圆曲线签名算法。当代理调用一个工具(如github_create_issue)时,Signet会构建一个包含以下信息的Action对象:

  • tool: 工具名称。
  • params: 调用参数(如{"title": "fix bug"})。
  • target: 操作目标URI(如mcp://github),用于绑定收据到特定资源。
  • transport: 传输协议。

这个Action对象,连同签名者信息(公钥、名称、所有者)和一个高精度时间戳、一个随机数(Nonce),被序列化为规范的JSON格式(遵循RFC 8785 JCS标准)。然后,使用代理的Ed25519私钥对整个JSON字符串进行签名,生成一个唯一的sig字段。这个签名与数据本身绑定,任何字节的修改都会使签名失效。

注意:关于随机数(Nonce)的重要性。随机数用于防止“重放攻击”。假设攻击者截获了一个有效的签名收据,他不能简单地重复发送这个收据来让服务器重复执行操作,因为服务器可以检查时间戳和随机数的唯一性。Signet在签名时自动生成随机数,并在验证时(如果服务端集成了验证)可以配置maxAge参数来拒绝过期的请求。

哈希链确保日志顺序与完整性单个收据可以防篡改,但攻击者可以删除或调换整个收据文件中的记录。为此,Signet的审计日志采用了哈希链结构。每一条新收据在写入日志时,其ID会包含前一条收据的哈希值。这就形成了一个密码学链接:Hash(Receipt_N) = F(Content_N, Hash(Receipt_N-1))。任何对历史记录的删除、插入或顺序调整,都会导致从破坏点开始的所有后续哈希值不匹配,在运行signet verify --chain或通过Dashboard检查时会被立刻发现。

这种设计意味着,即使攻击者完全控制了存储日志文件的服务器,他也无法在不被发现的情况下篡改历史。他只能追加新的、有效的记录。

2.2 架构定位:轻量级中间件而非重型网关

Signet明智地将自己定位为一个“层”(Layer),而非一个“平台”(Platform)。它不试图取代你的AI代理框架(如LangChain、CrewAI)、你的MCP(Model Context Protocol)服务器,或是你的日志聚合系统。相反,它通过SDK、插件和透明代理的方式,无侵入式地嵌入到你现有的工作流中。

透明签名代理模式这是Signet最巧妙的设计之一。对于MCP通信,你不需要修改你的代理代码或MCP服务器代码。你只需要在代理和MCP服务器之间插入一个SigningTransport包装器。这个包装器会拦截所有tools/call请求,自动为其生成签名收据并附加到请求的元数据中(如params._meta._signet),然后将请求原样转发。对于服务器而言,它收到的只是一个多了些元数据的普通请求,完全向后兼容。

// 示例:用SigningTransport包装任何MCP传输层 import { SigningTransport } from "@signet-auth/mcp"; const innerTransport = new StdioClientTransport({ command: "my-mcp-server" }); const signingTransport = new SigningTransport(innerTransport, agentSecretKey, "my-agent"); const client = new Client({}, {}); await client.connect(signingTransport); // 此后所有client.callTool()调用都会被自动签名

这种设计带来了巨大的灵活性。你可以:

  1. 仅客户端签名:快速获得审计线索,无需服务器配合。
  2. 服务端验证:如果你也控制服务器,可以添加verifyRequest逻辑,在执行工具前验证签名,拒绝非法请求。
  3. 双向签名:服务器在处理完请求后,可以用自己的私钥对代理的收据和响应结果进行“联合签名”,生成一个v3收据,证明“代理请求了X,我执行并返回了Y”。

2.3 信任模型的演进:从身份到授权

基础的签名解决了“谁做了什么事”的问题。但在企业环境中,更深层次的问题是:“谁允许这个代理做这件事?” 一个拥有私钥的代理可能是被入侵的,或者其权限可能已经过期。

Signet v4版本引入的委托链机制,正是为了回答这个问题。它模拟了现实世界中的授权体系:

  1. 根身份:一个人类用户或组织(如alice)持有根私钥。
  2. 委托令牌:根身份签署一个声明:“我将以下权限委托给公钥为0xABC...的代理,有效期24小时,仅限执行BashRead工具,目标为mcp://github。”
  3. 代理行动:代理在签署工具调用收据时,会附上这个委托令牌。
  4. 验证:验证者不仅检查代理的签名,还会递归验证整个委托链,确认根身份是否可信,以及代理的操作是否在委托的权限范围内。
# 创建委托令牌 signet delegate create --from alice --to deploy-bot --tools Bash,Read --ttl 24h # 代理使用委托链进行签名 signet delegate sign --key deploy-bot --tool Bash --chain delegation_token.json

这实现了权限的“最小化”和“时效性”。代理的权限被严格限定,并且会自动过期,极大地减少了凭证泄露带来的风险。

3. 核心组件与实操要点解析

理解了设计思路,我们深入拆解Signet的核心组件,看看在实际中如何配置和使用它们。我将结合自己搭建和调试的经验,分享一些文档中未提及的细节和“坑”。

3.1 身份与密钥管理:安全存储的权衡

一切始于一个身份。运行signet identity generate --name my-agent会在~/.signet/keys/目录下创建一个加密的密钥对。

密钥存储的两种模式与选择

  • 默认(加密):私钥使用用户提供的口令进行加密后存储。这是最安全的方式,适用于交互式环境(如开发机)。每次签名都需要输入口令或通过SIGNET_PASSPHRASE环境变量提供。
  • --unencrypted(非加密):私钥以明文形式存储。这非常危险,仅适用于高度受控的自动化环境,如CI/CD流水线。你必须确保该机器的磁盘和访问权限绝对安全。

实操心得:CI/CD中的密钥管理在GitHub Actions中,我通常这样做:

  1. 在仓库的Secrets中存储加密后的私钥字符串和口令。
  2. 在CI脚本中,创建~/.signet/keys/目录,将私钥文件写入。
  3. 通过SIGNET_PASSPHRASE环境变量传递口令。
  4. 关键步骤:在Job结束后,务必在post步骤中清理工作区,或使用Action的tmp目录,确保私钥文件不会残留。更好的做法是使用Signet的signet identity generate --unencrypted在CI运行时动态生成一个短期身份,并将公钥注册到目标系统。这样私钥只存在于内存中。

密钥的导出与分发公钥是需要分发给验证方的。使用signet identity export --name my-agent可以导出公钥信息。通常你会将公钥存储在:

  • 服务器的可信公钥列表配置中。
  • 审计系统的信任库。
  • 团队内部的密钥管理文档(如HashiCorp Vault)。

3.2 策略引擎:将合规要求代码化

签名证明了动作的来源和完整性,但没说明动作是否被允许。Signet的策略引擎允许你将合规与安全规则定义成YAML文件,并在签名前强制执行。

一个策略文件示例 (production-policy.yaml):

version: 1 name: production-agents default_action: deny # 默认拒绝,符合安全原则 rules: - id: allow-github-read match: tool: Read target: "mcp://github*" # 支持通配符 action: allow reason: "允许从GitHub仓库读取信息" - id: deny-destructive-bash match: tool: Bash params: command: contains: "rm -rf" contains: "format c:" # 防止Windows格式化命令 action: deny reason: "禁止执行高危删除命令" - id: allow-deploy-bot match: signer: name: "deploy-bot" tool: "github_create_issue" action: allow reason: "允许部署机器人创建Issue"

策略执行流程

  1. 验证signet policy validate production-policy.yaml检查语法并计算策略哈希。
  2. 检查signet policy check --tool Bash --params '{"command":"ls -la"}'进行干运行。
  3. 强制执行
    • CLIsignet sign --key my-agent --tool Bash --policy production-policy.yaml ...如果动作被拒绝,则根本不会生成收据。
    • 代理模式signet proxy --target my-server --key my-agent --policy production-policy.yaml代理会拦截并拒绝不符合策略的请求。

策略哈希与证明当动作被允许时,Signet不仅签名,还会将PolicyAttestation嵌入收据。这个证明包含了匹配的策略规则ID和整个策略文件的哈希值。这意味着你不仅可以证明“谁在何时做了什么”,还可以证明“这个动作是根据当时已生效的X号策略第Y条规则被允许的”。这对于合规审计是黄金证据。

注意事项:策略的版本控制策略文件会变。当你更新策略后,旧收据中的策略哈希将无法匹配新文件。因此,在审计时,你需要根据收据的时间戳,找到当时生效的策略文件版本进行验证。建议将策略文件纳入Git管理,并用收据中的时间戳关联到对应的Git提交。

3.3 MCP代理与执行边界验证:真正的安全防线

MCP代理是Signet的杀手级功能。它让你能为任何现有的MCP服务器瞬间加上签名和验证层。

配置一个简单的签名代理假设你有一个本地的GitHub MCP服务器在运行:

# 假设你的GitHub MCP服务器通过 `npx @myorg/github-server` 启动 signet proxy --target "npx @myorg/github-server" --key my-agent --policy production-policy.yaml

现在,这个代理进程会监听标准输入/输出。你将你的AI代理(如Claude Code)连接到这个代理,而不是直接连接到GitHub服务器。所有流量都会经过Signet的签名和策略检查。

服务端验证:构筑执行边界如果你同时控制MCP服务器,你应该在服务器端添加验证。这是防止伪造或重放请求的最后一道防线。

import { verifyRequest } from "@signet-auth/mcp-server"; import { CallToolRequestSchema } from "@modelcontextprotocol/sdk/types"; server.setRequestHandler(CallToolRequestSchema, async (request) => { // 1. 验证请求 const verification = verifyRequest(request, { trustedKeys: [process.env.TRUSTED_AGENT_PUBLIC_KEY], maxAge: 300, // 收据有效期5分钟 expectedTarget: "mcp://github", // 可选:检查目标是否匹配 }); // 2. 处理验证结果 if (!verification.ok) { // 签名无效、格式错误等 console.error(`签名验证失败: ${verification.error}`); return { content: [{ type: "text", text: "Invalid request signature" }], isError: true }; } if (!verification.trusted) { // 签名有效,但签名者不在信任列表 console.warn(`未受信任的签名者: ${verification.signerName}`); return { content: [{ type: "text", text: "Untrusted agent" }], isError: true }; } if (verification.age && verification.age > 300) { // 请求过于陈旧,可能为重放 return { content: [{ type: "text", text: "Request too old" }], isError: true }; } // 3. 验证通过,执行工具 console.log(`来自可信代理 ${verification.signerName} 的请求已验证`); const result = await callTool(request.params); // 4. (可选) 服务器联合签名响应,生成v3收据 const bilateralReceipt = signBilateral(serverSecretKey, verification.receipt, result, "github-server"); // 可以将 bilateralReceipt 记录到服务器审计日志 return result; });

执行边界验证的价值这实现了“零信任”架构在AI代理层面的应用。服务器不再盲目信任来自网络的请求,而是要求每一个请求都附带一个新鲜、有效、来自可信源的密码学证明。这能有效抵御:

  • 凭证泄露:即使攻击者获得了MCP服务器的访问令牌,没有代理的私钥也无法伪造签名请求。
  • 中间人攻击:篡改请求内容会导致签名失效。
  • 重放攻击:过期的收据会被maxAge检查拒绝。

4. 与主流AI框架的集成实战

Signet的价值在于其易集成性。下面我将以几个最流行的框架为例,展示如何快速接入。

4.1 LangChain / LangGraph 集成

LangChain通过回调系统提供了完美的钩子。Signet的SignetCallbackHandler可以无缝接入。

from langchain.agents import AgentExecutor, create_openai_tools_agent from langchain_openai import ChatOpenAI from signet_auth import SigningAgent from signet_auth.langchain import SignetCallbackHandler # 1. 创建Signet代理 agent = SigningAgent("langchain-agent", owner="dev-team") # 2. 创建回调处理器 signet_handler = SignetCallbackHandler(agent) # 3. 配置LangChain Agent llm = ChatOpenAI(model="gpt-4", temperature=0) tools = [get_github_tool(), get_sql_tool()] agent_executor = AgentExecutor(agent=create_openai_tools_agent(llm, tools), tools=tools) # 4. 执行,并传入回调 result = agent_executor.invoke( {"input": "在仓库X中创建一个标题为'Bug Fix'的Issue"}, config={"callbacks": [signet_handler]} # 关键:传入回调 ) # 5. 查看收据 print(f"本次执行生成了 {len(signet_handler.receipts)} 张收据") for receipt in signet_handler.receipts: print(f"- {receipt['action']['tool']}: {receipt['id']}")

异步支持:对于AsyncAgentExecutor,使用AsyncSignetCallbackHandler

踩坑记录:LangChain工具调用的粒度LangChain的Agent在一次invoke中可能会进行多轮“思考-行动-观察”的循环。SignetCallbackHandler会在每一次工具被实际调用时触发签名。这意味着一个复杂的Agent任务可能会产生多张收据,完整记录了其决策过程。这对于调试和审计Agent的行为逻辑非常有价值。

4.2 CrewAI 集成

CrewAI的集成更为“全局化”。通过install_hooks函数,可以一次性为所有Agent和Task安装签名钩子。

from crewai import Agent, Task, Crew from signet_auth import SigningAgent from signet_auth.crewai import install_hooks # 1. 创建Signet代理 signet_agent = SigningAgent("crewai-supervisor", owner="team-alpha") # 2. 安装全局钩子 (这行代码必须放在定义Crew之前) install_hooks(signet_agent) # 3. 正常定义你的Crew researcher = Agent( role="研究员", goal="研究最新AI安全趋势", backstory="...", tools=[web_search_tool] ) writer = Agent(role="写手", goal="撰写报告", backstory="...", tools=[document_tool]) research_task = Task(description="研究主题X", agent=researcher) write_task = Task(description="撰写报告", agent=writer) crew = Crew(agents=[researcher, writer], tasks=[research_task, write_task]) # 4. 执行。所有工具调用都会被自动签名并记录到 ~/.signet/audit/ result = crew.kickoff()

这种全局集成方式非常简洁,但需要注意它会给Crew中所有的工具调用签名。如果你的Crew中有些工具调用不需要审计,可能需要更精细的控制,或者考虑使用基于LangChain的集成方式(CrewAI底层也使用LangChain)。

4.3 与 Claude Code 和 Cursor 等AI编码助手的集成

对于像Claude Code、Cursor这类直接集成在IDE中的AI编码助手,Signet通过官方插件市场提供了一键式安装。

在Claude Code中安装:

  1. 在Chat输入框,键入:/plugin install signet@claude-plugins-official
  2. 安装完成后,Claude Code使用工具(如运行终端命令、读写文件)时,会自动调用Signet插件进行签名。
  3. 所有收据默认存储在~/.signet/audit/目录下。

工作原理:插件利用了Claude Code的PostToolUse钩子。每当Claude执行一个工具(如Bash、Filesystem),插件就会捕获该事件,调用Signet的WASM绑定库生成收据,并追加到本地审计日志。整个过程对用户透明。

实操心得:IDE插件的审计价值在开发过程中,我曾遇到一个由AI助手自动执行的git reset --hard命令误删了未提交的代码。因为没有历史记录,恢复非常困难。接入Signet后,所有的AI操作都被记录。现在,我可以随时运行signet audit --since 1h --tool Bash来查看过去一小时AI执行了哪些命令,快速定位问题源头。这对于团队协作和新人培训尤其有用,可以清晰看到AI是如何被使用的。

4.4 在Vercel AI SDK中集成

如果你使用Vercel AI SDK构建AI应用,Signet提供了专门的中间件。

import { generateText } from "ai"; import { openai } from "@ai-sdk/openai"; import { generateKeypair } from "@signet-auth/core"; import { createSignetCallbacks } from "@signet-auth/vercel-ai"; // 1. 生成或加载代理密钥 const { secretKey, publicKey } = generateKeypair(); // 在实际应用中,密钥应从安全存储中加载 // 2. 创建Signet回调 const signetCallbacks = createSignetCallbacks(secretKey, "vercel-ai-agent"); // 3. 在调用AI时传入回调 const { text, toolCalls, toolResults } = await generateText({ model: openai("gpt-4o"), tools: { getWeather: { description: "获取天气", parameters: { city: { type: "string" } }, execute: async ({ city }) => ({ weather: `Sunny in ${city}` }), }, }, ...signetCallbacks, // 展开所有必要的回调函数 prompt: "What's the weather in Shanghai?", }); // 4. 收据存储在 callbacks.receipts 中 console.log("Tool call receipts:", signetCallbacks.receipts);

Vercel AI SDK的集成将签名过程嵌入到SDK的工具调用生命周期中,确保了在流式响应等复杂场景下也能正确捕获和签名每一次工具调用。

5. 审计、排查与运维实践

生成收据只是第一步,如何有效地管理和利用这些审计数据才是体现价值的关键。

5.1 使用CLI进行日常审计

Signet CLI提供了强大的审计查询功能。

基本查询:

  • signet audit:查看最近的收据。
  • signet audit --since 24h:查看过去24小时内的收据。
  • signet audit --tool github:查看所有工具名包含“github”的收据。
  • signet audit --signer deploy-bot:查看特定签名者的所有操作。

高级验证与导出:

  • signet audit --verify:不仅列出收据,还逐一验证其签名。这是定期巡检的好方法。
  • signet audit --export audit_export.json:将审计日志导出为JSON格式,便于导入到SIEM(安全信息与事件管理)系统或自定义分析平台。
  • signet verify --chain:这是最重要的命令之一。它会遍历整个审计日志,计算每条记录的哈希链,确保从第一条记录到现在,没有任何记录被篡改、删除或插入。如果链是完整的,你会看到Chain integrity verified的输出。

5.2 可视化仪表盘

对于不喜欢命令行的人来说,signet dashboard命令会启动一个本地Web服务器(默认在http://localhost:8080),打开一个交互式审计仪表盘。

仪表盘核心功能:

  1. 时间线视图:以卡片形式按时间倒序列出所有工具调用,清晰展示工具、签名者、目标和状态。
  2. 链完整性检查:图形化展示哈希链,任何断裂处都会用红色高亮显示,并指出断裂发生在哪个文件的哪一行。这对于调查可疑活动至关重要。
  3. 统计信息:按工具、签名者、收据版本进行聚合统计,帮你了解代理的行为模式。
  4. 详细检视:点击任意收据,可以查看其完整的JSON内容、签名详情、以及内嵌的策略证明(如果有)。

这个仪表盘完全在本地运行,无需网络连接,所有数据都来自你的~/.signet/audit/目录,确保了审计数据的私密性。

5.3 常见问题排查实录

在实际部署中,你可能会遇到以下问题:

问题1:签名验证失败 (signet verify返回错误)

  • 可能原因A:时钟偏差。签名包含高精度时间戳。如果生成收据的机器和验证收据的机器系统时间相差太大(超过maxAge容忍范围),验证会失败。
    • 解决:确保所有机器使用NTP服务同步时间。
  • 可能原因B:公钥不匹配。你用来验证的公钥和签名时使用的私钥不是一对。
    • 解决:使用signet identity export确认你正在使用正确的公钥。检查验证命令中的--pubkey参数或环境变量SIGNET_TRUSTED_KEYS
  • 可能原因C:收据文件被损坏或手动编辑过
    • 解决:尝试从备份或另一台机器的审计日志中获取原始收据。任何对收据JSON的修改(包括美化格式)都会破坏签名。

问题2:MCP代理模式下,工具调用失败,服务器返回“未签名”错误

  • 可能原因A:SigningTransport未正确包装传输层。确保你的客户端连接的是Signet代理的端口/stdio,而不是直接连接原始服务器。
  • 可能原因B:代理密钥未加载或口令错误。检查--key参数指定的身份是否存在,且SIGNET_PASSPHRASE环境变量设置正确(如果密钥是加密的)。
  • 可能原因C:网络策略或防火墙阻止了代理与服务器之间的通信
    • 解决:检查代理进程的日志输出。使用telnetcurl测试代理监听的端口是否可达。

问题3:审计日志文件 (~/.signet/audit/*.ndjson) 增长过快

  • 可能原因:高频工具调用产生大量收据。
  • 解决方案
    1. 日志轮转:Signet目前不内置轮转,但你可以使用Linux的logrotate工具或一个简单的cron任务,定期压缩旧的日志文件并移走。注意:移动或压缩文件会破坏哈希链。在执行轮转后,你应该运行一次signet verify --chain,它会检测到链的终点,然后从新的空文件开始新的链。这是可接受的操作。
    2. 选择性签名:并非所有工具调用都需要审计。你可以在框架层进行过滤,只为重要的、有风险的操作(如写数据库、调用外部API、执行Shell命令)启用Signet回调。
    3. 使用--hash-only模式:在signet sign时添加--hash-only标志,收据中将只存储params的哈希值,而不是完整的参数JSON。这能显著减少日志体积,但代价是你无法直接从收据中看到具体的参数内容,需要额外的元数据存储来关联哈希和原始参数。

问题4:在Docker容器中运行Signet,密钥和审计日志如何持久化?

  • 最佳实践
    1. 密钥:通过Docker Secrets或环境变量(对于CI)注入,切勿将密钥硬编码在镜像中。或者在容器启动时动态生成身份(signet identity generate --unencrypted),并将公钥注册到外部系统。
    2. 审计日志:将~/.signet/audit/目录挂载为Docker Volume,例如-v ./signet-audit:/root/.signet/audit。这样日志可以持久化在宿主机上,并且可以被其他工具(如日志收集器)访问。
    3. 注意用户:确保容器内运行Signet的用户对挂载的卷有读写权限。

6. 进阶场景:委托链与跨组织审计

对于更复杂的生产环境,尤其是涉及多个团队或外部合作伙伴的场景,基础的代理签名可能不够。你需要清晰的授权追溯。

场景:平台团队(platform-team)需要授权一个来自数据科学团队(># 平台团队使用其根身份 (platform-root) 创建委托令牌 signet delegate create \ --from platform-root \ --to>signet delegate sign \ --key>const verification = verifyRequest(request, { trustedKeys: [], // 不直接信任>

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询