薄く、依存関係ゼロのTypeScriptクライアント for 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: 'Wire transfer to vendor',
payload: { to: 'acct_8821', amount_usd: 4200, memo: 'INV-2025-118' },
notify: 'telegram_or_email',
},
{ onCreated: (c) => console.log('Approve here:', c.url) },
);
if (decision.status === 'approved') {
// proceed — decision.rawAssertion is the signed proof
} else {
// 'rejected' | 'expired' | 'pending' (timed out)
}
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 }); // long-poll
idempotencyKey を(Idempotency-Key ヘッダーとして送信)指定すると、再試行された作成(例:ネットワークの一時的な中断によりロングポーリングが中断された後)が、同じキーと同じアクション/ペイロードで行われた場合、重複を作成したり承認者に再通知したりせず、元の確認(idempotent: true)を返します。異なるパラメータでキーを再利用すると 422 で拒否されます。
await cosignet.createConfirmation({
username: 'alex',
action: 'Wire transfer to vendor',
payload: { to: 'acct_8821', amount_usd: 4200 },
idempotencyKey: 'wire-INV-2025-118', // stable per logical operation
});
returnUrl を渡すと、リクエストが解決(承認/拒否/期限切れ)した後に、承認ページに "Return to <host>" ボタンが表示されます — 人間がアプリに戻ってタイムアウトしたアクションを再トリガーできるので便利です。これは https URL である必要があり、クリックスルーリンクとしてのみレンダリングされます(サーバー側でフェッチされることはありません)。
await cosignet.requestApproval({
username: 'alex',
action: 'Wire transfer to vendor',
payload: { to: 'acct_8821', amount_usd: 4200 },
returnUrl: 'https://app.example.com/approvals',
});
notify('none' | 'telegram' | 'email' | 'telegram_or_email')は、特定の署名者への個人連絡 ping を制御します:
telegram — 承認者のリンクされたTelegramへのDM(telegram_or_emailは承認者がチャットをリンクしている場合にこれを優先)。email — 承認者自身のアドレスへのメール。優先順位:ダッシュボードで承認者に設定した直接メール(配信のみ)、次に確認済みメンバーメール、最後にアカウントの連絡先アドレス。telegram_or_email — リンクされていればTelegram、そうでなければ上記のメール。none — 個人へのpingなし。別途、チーム連絡はダッシュボードでアカウントごとに設定された共有チャンネルへのSlackブロードキャストです。これは追加的なもので、notify とは独立して設定されていれば常に発動します(そのため notify:'none' でもSlackに投稿されます)。承認者のTelegramとメールはダッシュボードの承認者セクションで設定します。
public: trueの公開に関する注意: 透明性ログにコミットされるメールは、アカウントの指定された確認済みメール(ダッシュボード設定で選択された、責任当事者としてのもの — 個別の署名者ではありません)です。直接通知メールは配信専用であり、公開ハッシュには使用されません。
すべてのチャンネルはリンクのみ:通知は承認URLを伝え、action や payload は含まれません。承認は常に確認ページで承認者のペイロードにバインドされたパスキーを必要とします — チャンネル内での承認はありません。
verifyWebhookSignature は、生のリクエストボディの16進数HMAC-SHA256を再計算し、Cosignet-Signature ヘッダーと定数時間で比較します。受信した正確なバイトを渡してください — JSONを再シリアライズするとシグネチャが変わります。
import { verifyWebhookSignature } from '@cosignet/sdk';
const ok = await verifyWebhookSignature({
body: rawBody, // raw string, not re-parsed JSON
signature: req.headers['cosignet-signature'],
secret: process.env.COSIGNET_WEBHOOK_SECRET!,
timestamp: req.headers['cosignet-timestamp'], // optional replay protection
toleranceSeconds: 300, // optional
});
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ハッシュがマークルリーフにコミットされ、誰でも候補アドレスを照合できます。
await cosignet.requestApproval({
username: 'alice',
action: 'Publish Q3 board resolution',
payload: { docId: 'res-2026-Q3' },
public: true, // permanent, irreversible — see caveats below
});
examples/ — コピー&ペースト可能なcURL、Node、Python、GitLab CI、MCP/tool-wrapper、およびWorkerの例。security/ — ペイロードバインディング、WebAuthn証明モデル、脅威モデル、透明性ログ、責任ある開示。高リスクアクションをパスキー承認の背後にゲート:
recipes/ai-agent-gate — AIコーディングエージェント(Claude Code)が危険なシェルコマンドを実行する前に一時停止し、人間が共同署名するまで待機。recipes/cli-approval — 任意のコマンド(例:terraform destroy)をラップ。recipes/ci-cd — パイプライン(GitHub Actions / GitLab CI)を承認でブロック。recipes/backend-service — サービス内の不可逆的な操作を一時停止。[email protected] にメール;脆弱性については公開issueを作成しない。[email protected]。MIT © Cosignet
| シグナル | 意味 | あなたのアクション |
|---|
status: approved(署名が検証済み) | 人間がこの正確なペイロードを承認 | 続行 |
status: rejected | 人間が拒否 | 実行しない;リクエスタに表示 |
status: expired | 誰も時間内に決定しなかった | 実行しない;まだ必要な場合は再リクエスト |
| タイムアウト、ネットワークエラー、または5xx | 不明な状態 | 実行しない;バックオフでリトライ;N回の失敗後にアラート |