轻量、零依赖的 TypeScript 客户端,用于 Cosignet — 人在回路中 批准高风险 AI 代理操作, 并附带绑定负载的通行密钥签名。
在危险操作执行前引入人工审批:暂停操作,获取一个人的明确通行密钥批准(Face ID / Touch ID / Windows Hello / 安全密钥),仅在签署的决策 绑定到确切负载 后才继续执行——事后修改操作则签名不再匹配。Cosignet 是一个审批和证据层,而非执行器或策略引擎。可在任何支持全局 fetch + Web Crypto 的环境中运行:Node 18+、Cloudflare Workers、Deno 和浏览器。
状态: 早期访问。已发布到 npm 上,名为
@cosignet/sdk。
npm install @cosignet/sdk
import { Cosignet } from '@cosignet/sdk';
const cosignet = new Cosignet({ apiKey: process.env.COSIGNET_API_KEY! });
const decision = await cosignet.requestApproval(
{
username: 'alex',
action: '向供应商电汇',
payload: { to: 'acct_8821', amount_usd: 4200, memo: 'INV-2025-118' },
notify: 'telegram_or_email',
},
{ onCreated: (c) => console.log('在此处审批:', c.url) },
);
if (decision.status === 'approved') {
// 继续——decision.rawAssertion 是签名后的证明
} else {
// 'rejected' | 'expired' | 'pending'(超时)
}
requestApproval 通过你自己的出站连接进行长轮询(约25秒一跳),因此它能在 CLI 工具和处于 NAT/防火墙后的锁定 VPC 中工作——无需入站 webhook、开放端口或公网 IP。
如果你的系统无法获得已签名的批准,那么被限制的操作绝对不能执行。不可用性绝不能降级为自动批准。将所有超时、网络错误和5xx视为“未批准”。
实现注意事项:
详见 当 Cosignet 不可用时 获取完整的可用性和紧急预案指导。
const created = await cosignet.createConfirmation({ username, action, payload });
const status = await cosignet.getConfirmation(created.id, { wait: 25 }); // 长轮询
传递 idempotencyKey(作为 Idempotency-Key 头发送),这样重试创建时——例如网络问题中断了长轮询——使用 相同的键和相同的 action/payload 会返回 原始 确认(idempotent: true),而不会生成重复确认或重新通知审批人。使用不同的参数重用键会被拒绝并返回 422。
await cosignet.createConfirmation({
username: 'alex',
action: '向供应商电汇',
payload: { to: 'acct_8821', amount_usd: 4200 },
idempotencyKey: 'wire-INV-2025-118', // 每个逻辑操作稳定
});
传递 returnUrl 可以在决议(批准/拒绝/过期)后,在审批页面上显示一个 "返回到 <host>" 按钮——方便人类返回你的应用以重新触发已超时的操作。它必须是 https URL,且仅作为点击链接渲染(永不服务端获取)。
await cosignet.requestApproval({
username: 'alex',
action: '向供应商电汇',
payload: { to: 'acct_8821', amount_usd: 4200 },
returnUrl: 'https://app.example.com/approvals',
});
notify('none' | 'telegram' | 'email' | 'telegram_or_email')控制向特定签署人发送的 个人主动触达 通知:
telegram —— 向审批人关联的 Telegram 发送私信(telegram_or_email 在审批人已关联聊天时优先使用这种方式)。email —— 向 审批人自己的地址 发送电子邮件。优先级:你在仪表板中为审批人设置的直接电子邮件(仅用于投递),然后是已验证的成员邮箱,最后是账户联系地址(作为最后手段)。telegram_or_email —— 如果关联了 Telegram 则用 Telegram,否则用上述电子邮件。none —— 不发送个人通知。另外,团队触达 是通过 Slack 广播到共享频道,在仪表板中按账户配置。它是附加的,配置后始终触发,与 notify 无关(因此 notify:'none' 仍会发布到 Slack)。审批人通过仪表板的“审批人”部分设置其 Telegram 和电子邮件。
关于
public: true的揭示说明: 提交到透明度日志中的电子邮件是 账户指定的已验证电子邮件(在仪表板设置中选择,作为责任方——不是个人签署人)。直接通知邮件仅用于投递,从不用于揭示哈希。
每个频道 仅包含链接:通知携带审批 URL,绝不包含 action 或 payload。审批始终要求审批人在确认页面上使用绑定负载的通行密钥——不存在在频道内批准的方式。
verifyWebhookSignature 重新计算 原始 请求主体的十六进制 HMAC-SHA256,并与 Cosignet-Signature 头部进行恒定时间比较。传递你收到的确切字节——重新序列化 JSON 会改变签名。
import { verifyWebhookSignature } from '@cosignet/sdk';
const ok = await verifyWebhookSignature({
body: rawBody, // 原始字符串,不是解析后的 JSON
signature: req.headers['cosignet-signature'],
secret: process.env.COSIGNET_WEBHOOK_SECRET!,
timestamp: req.headers['cosignet-timestamp'], // 可选重放保护
toleranceSeconds: 300, // 可选
});
if (!ok) return res.status(401).end();
详见 docs/verification.md 获取调用方检查清单:仅在 approved 时继续,将决策与你即将执行的操作进行比对,并将拒绝/过期/超时视为硬性中止。
new Cosignet({ apiKey, baseUrl?, fetch? })createConfirmation(input) → CreatedConfirmationgetConfirmation(id, { wait? }) → ConfirmationrequestApproval(input, { timeoutMs?, onCreated? }) → ConfirmationverifyWebhookSignature({ body, signature, secret, timestamp?, toleranceSeconds? }) → Promise<boolean>默认情况下,透明度日志会隐藏负载,不暴露任何审批人身份。在 createConfirmation/requestApproval 上设置 public: true 可将单个审批选择加入 公开揭示:其原始的 action + payload 将在验证包中可读,并且账户指定的已验证电子邮件(在仪表板设置中选择——责任方,不是个人签署人)的 PBKDF2 哈希值将被提交到 Merkle 叶子中,以便任何人可以检查候选地址是否匹配。
await cosignet.requestApproval({
username: 'alice',
action: '发布 Q3 董事会决议',
payload: { docId: 'res-2026-Q3' },
public: true, // 永久且不可逆——请参见下面的注意事项
});
examples/ —— 可复制的 cURL、Node、Python、GitLab CI、MCP/工具封装和 Worker 示例。security/ —— 负载绑定、WebAuthn 证明模型、威胁模型、透明度日志和负责任披露。使用通行密钥审批限制高风险操作:
recipes/ai-agent-gate —— 在 AI 编码代理(Claude Code)运行危险 shell 命令前暂停,直到人类共同签名。recipes/cli-approval —— 包装任意命令(例如 terraform destroy)。recipes/ci-cd —— 在批准前阻止管道(GitHub Actions / GitLab CI)。recipes/backend-service —— 暂停服务中的不可逆操作。[email protected];请勿就漏洞提交公开问题。[email protected]。MIT © Cosignet
| 信号 | 含义 | 你的操作 |
|---|
status: approved(签名验证通过) | 人类批准了这个确切负载 | 继续 |
status: rejected | 人类拒绝 | 不执行;向请求者反馈 |
status: expired | 无人及时决定 | 不执行;若仍需则重新请求 |
| 超时、网络错误或5xx | 状态未知 | 不执行;重试并退避;N次失败后告警 |