
Schlanker TypeScript + Python-Client ohne Abhängigkeiten und Rezepte, um risikoreiche Aktionen durch eine nutzlastgebundene Passkey-Genehmigung abzusichern.
Schlanker TypeScript-Client ohne Abhängigkeiten für Cosignet — Human-in-the-Loop-Freigabe für risikoreiche KI-Agenten-Aktionen, mit nutzlastgebundenen Passkey-Signaturen.
Bringe einen Menschen in den Kreislauf, bevor eine gefährliche Aktion ausgeführt wird: pausiere sie, hole eine explizite Passkey-Genehmigung von einer Person (Face ID / Touch ID / Windows Hello / Sicherheitsschlüssel) ein und fahre nur mit einer signierten Entscheidung fort, die an die exakte Nutzlast gebunden ist – ändere die Aktion danach und die Signatur stimmt nicht mehr. Cosignet ist eine Genehmigungs- und Beweisschicht, kein Ausführer oder Policy-Engine. Läuft überall mit globalem fetch + Web Crypto: Node 18+, Cloudflare Workers, Deno und Browser.
Status: Früher Zugang. Veröffentlicht auf npm als
@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('Hier genehmigen:', c.url) },
);
if (decision.status === 'approved') {
// fortfahren — decision.rawAssertion ist der signierte Beweis
} else {
// 'rejected' | 'expired' | 'pending' (timeout)
}
requestApproval führt Long-Polling über Ihre eigene ausgehende Verbindung durch (~25s Hops), sodass es auch von CLI-Tools und abgeschotteten VPCs hinter NAT/Firewalls funktioniert – kein eingehender Webhook, kein offener Port, keine öffentliche IP erforderlich.
Wenn Ihr System keine signierte Genehmigung erhalten kann, darf die abgesicherte Aktion nicht ausgeführt werden. Nichtverfügbarkeit darf niemals zu automatischer Genehmigung führen. Behandle jedes Timeout, jeden Netzwerkfehler und jeden 5xx als "nicht genehmigt".
Implementierungshinweise:
Siehe Wenn Cosignet nicht verfügbar ist für die vollständige Anleitung zur Verfügbarkeit und zum Notfallzugriff.
const created = await cosignet.createConfirmation({ username, action, payload });
const status = await cosignet.getConfirmation(created.id, { wait: 25 }); // Long-Poll
Übergeben Sie idempotencyKey (gesendet als Idempotency-Key-Header), sodass ein wiederholter create-Vorgang – z. B. nach einem Netzwerkausfall, der ein Long-Poll unterbricht – mit gleichem Schlüssel und gleicher Aktion/Nutzlast die ursprüngliche Bestätigung (idempotent: true) zurückgibt, anstatt ein Duplikat zu erzeugen oder den Genehmiger erneut zu benachrichtigen. Die Wiederverwendung eines Schlüssels mit anderen Parametern wird mit einem 422 abgelehnt.
await cosignet.createConfirmation({
username: 'alex',
action: 'Wire transfer to vendor',
payload: { to: 'acct_8821', amount_usd: 4200 },
idempotencyKey: 'wire-INV-2025-118', // stabil pro logischem Vorgang
});
Übergeben Sie returnUrl, um auf der Genehmigungsseite einen "Zurück zu "-Button anzuzeigen, sobald die Anfrage aufgelöst wurde (genehmigt / abgelehnt / abgelaufen) – praktisch, damit ein Mensch zurück zu Ihrer App gelangen kann, um eine abgelaufene Aktion erneut auszulösen. Es muss eine HTTPS-URL sein und wird nur als Klick-Link dargestellt (niemals serverseitig abgerufen).
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') steuert den persönlichen Erreichbarkeits-Ping an den spezifischen Unterzeichner:
telegram – eine DM an das verknüpfte Telegram des Genehmigers (telegram_or_email bevorzugt dies, wenn der Genehmiger einen Chat verknüpft hat).email – eine E-Mail an die eigene Adresse des Genehmigers. Priorität: die direkte E-Mail, die Sie dem Genehmiger im Dashboard zugewiesen haben (nur zum Versand), dann die verifizierte Mitglieds-E-Mail, dann als letzte Möglichkeit die Kontaktadresse des Kontos.telegram_or_email – Telegram, falls verknüpft, ansonsten die obige E-Mail.none – kein persönlicher Ping.Getrennt davon ist die Team-Erreichbarkeit eine Slack-Broadcast-Nachricht an einen gemeinsamen Kanal, die pro Konto im Dashboard konfiguriert wird. Sie ist additiv und wird bei Konfiguration immer ausgelöst, unabhängig von notify (d.h. selbst bei notify:'none' wird noch an Slack gesendet). Genehmiger erhalten ihr Telegram und ihre E-Mail im Bereich "Genehmiger" des Dashboards.
Hinweis zur Veröffentlichung mit
public: true: Die in das Transparenzprotokoll aufgenommene E-Mail ist die vom Konto festgelegte verifizierte E-Mail-Adresse (gewählt in den Dashboard-Einstellungen, als verantwortliche Partei – nicht der einzelne Unterzeichner). Die direkte Benachrichtigungs-E-Mail dient nur zum Versand und wird niemals für den Offenlegungs-Hash verwendet.
Jeder Kanal ist rein als Link: Die Benachrichtigung enthält die Genehmigungs-URL und niemals die action oder payload. Die Genehmigung erfordert immer den nutzlastgebundenen Passkey des Genehmigers auf der Bestätigungsseite – es gibt keine Genehmigung im Kanal.
verifyWebhookSignature berechnet den hex HMAC-SHA256 des rohen Anforderungstextes neu und vergleicht ihn konstantenzeitlich mit dem Cosignet-Signature-Header. Übergeben Sie die exakten Bytes, die Sie empfangen haben – erneutes Serialisieren von JSON ändert die Signatur.
import { verifyWebhookSignature } from '@cosignet/sdk';
const ok = await verifyWebhookSignature({
body: rawBody, // roher String, kein neu-geparstes JSON
signature: req.headers['cosignet-signature'],
secret: process.env.COSIGNET_WEBHOOK_SECRET!,
timestamp: req.headers['cosignet-timestamp'], // optionaler Replay-Schutz
toleranceSeconds: 300, // optional
});
if (!ok) return res.status(401).end();
Siehe docs/verification.md für die aufruferseitige Checkliste: nur bei approved fortfahren, die Entscheidung mit der durchzuführenden Operation vergleichen und rejected/expired/timeouts als harte Stopps behandeln.
new Cosignet({ apiKey, baseUrl?, fetch? })createConfirmation(input) → CreatedConfirmationgetConfirmation(id, { wait? }) → ConfirmationrequestApproval(input, { timeoutMs?, onCreated? }) → ConfirmationverifyWebhookSignature({ body, signature, secret, timestamp?, toleranceSeconds? }) → Promise<boolean>Standardmäßig hält das Transparenzprotokoll die Nutzlast zurück und gibt keine Identität des Genehmigers preis. Setzen Sie public: true bei createConfirmation/requestApproval, um eine einzelne Genehmigung für die öffentliche Offenlegung zu aktivieren: Ihre rohe action + payload werden im Verifikationspaket lesbar, und ein PBKDF2-Hash der vom Konto festgelegten verifizierten E-Mail-Adresse (gewählt in den Dashboard-Einstellungen – die verantwortliche Partei, nicht der einzelne Unterzeichner) wird in das Merkle-Blatt aufgenommen, sodass jeder eine Kandidatenadresse dagegen prüfen kann.
await cosignet.requestApproval({
username: 'alice',
action: 'Publish Q3 board resolution',
payload: { docId: 'res-2026-Q3' },
public: true, // dauerhaft und unumkehrbar – siehe Hinweise unten
});
examples/ – kopierfertige Beispiele für cURL, Node, Python, GitLab CI, MCP/Tool-Wrapper und Worker.security/ – Nutzlastbindung, WebAuthn-Beweismodell, Bedrohungsmodell, Transparenzprotokoll und verantwortungsvolle Offenlegung.Sperren Sie risikoreiche Aktionen hinter einer Passkey-Genehmigung:
recipes/ai-agent-gate – pausieren Sie einen KI-Codierungsagenten (Claude Code), bevor er einen gefährlichen Shell-Befehl ausführt, bis ein Mensch gegenzeichnet.recipes/cli-approval – umschließen Sie beliebige Befehle (z. B. terraform destroy).recipes/ci-cd – blockieren Sie eine Pipeline (GitHub Actions / GitLab CI) bis zur Genehmigung.recipes/backend-service – pausieren Sie einen irreversiblen Vorgang in einem Dienst.[email protected]; reichen Sie keine öffentlichen Issues für Schwachstellen ein.[email protected].MIT © Cosignet
| Signal | Bedeutung | Ihre Aktion |
|---|
status: approved (Signatur verifiziert) | Mensch hat diese exakte Nutzlast genehmigt | Fortfahren |
status: rejected | Mensch hat abgelehnt | Nicht ausführen; dem Antragsteller melden |
status: expired | Niemand hat rechtzeitig entschieden | Nicht ausführen; bei Bedarf erneut anfordern |
| Timeout, Netzwerkfehler oder 5xx | Unbekannter Status | Nicht ausführen; mit Backoff wiederholen; nach N Fehlern alarmieren |