
Cliente TypeScript ligero + Python sin dependencias y recetas para proteger acciones de alto riesgo detrás de una aprobación de clave de acceso vinculada a la carga útil.
Cliente TypeScript ligero y sin dependencias para Cosignet — aprobación humano en el bucle para acciones de agentes de IA de alto riesgo, con firmas de passkey vinculadas al payload.
Ponga a un humano en el bucle antes de que se ejecute una acción peligrosa: pausarla, obtener una aprobación explícita de passkey de una persona (Face ID / Touch ID / Windows Hello / clave de seguridad) y continuar solo con una decisión firmada vinculada al payload exacto — si cambia la acción después, la firma ya no coincidirá. Cosignet es una capa de aprobación y evidencia, no un ejecutor o motor de políticas. Se ejecuta en cualquier lugar con fetch global + Web Crypto: Node 18+, Cloudflare Workers, Deno y navegadores.
Estado: acceso temprano. Publicado en npm como
@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 realiza long-polling sobre su propia conexión saliente (saltos de ~25s), por lo que funciona desde herramientas CLI y VPCs bloqueadas detrás de NAT/firewalls — sin necesidad de webhook entrante, puerto abierto o IP pública.
Si su sistema no puede obtener una aprobación firmada, la acción bloqueada no debe ejecutarse. La falta de disponibilidad nunca debe degradarse a una autoaprobación. Trate cada tiempo de espera, error de red y 5xx como "no aprobado".
Notas de implementación:
Consulte Cuando Cosignet no está disponible para obtener la guía completa de disponibilidad y emergencia.
const created = await cosignet.createConfirmation({ username, action, payload });
const status = await cosignet.getConfirmation(created.id, { wait: 25 }); // long-poll
Pase idempotencyKey (enviado como el encabezado Idempotency-Key) para que una creación reintentada — por ejemplo, después de un parpadeo de red que interrumpe un long-poll — con la misma clave y la misma acción/payload devuelva la confirmación original (idempotent: true) en lugar de crear un duplicado o volver a notificar al aprobador. Reutilizar una clave con parámetros diferentes es rechazado con un 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
});
Pase returnUrl para mostrar un botón "Volver a <host>" en la página de aprobación una vez que la solicitud se resuelva (aprobada / rechazada / expirada) — útil para que un humano pueda volver a su aplicación para reactivar una acción que expiró. Debe ser una URL https y solo se renderiza como un enlace de clic (nunca se obtiene del lado del servidor).
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') controla el ping de alcance personal al firmante específico:
telegram — un MD al Telegram vinculado del aprobador (telegram_or_email prefiere esto cuando el aprobador ha vinculado un chat).email — un correo electrónico a la dirección propia del aprobador. Prioridad: el correo directo que configuró para el aprobador en el panel (solo entrega), luego su correo de miembro verificado, luego la dirección de contacto de la cuenta como último recurso.telegram_or_email — Telegram si está vinculado, de lo contrario el correo anterior.none — sin ping personal.Por separado, alcance de equipo es una transmisión de Slack a un canal compartido, configurada por cuenta en el panel. Es aditiva y siempre se activa cuando está configurada, independientemente de notify (por lo que notify:'none' aún publica en Slack). Los aprobadores obtienen su Telegram y correo configurados desde la sección de Aprobadores del panel.
Nota sobre la revelación
public: true: el correo electrónico comprometido en el registro de transparencia es el correo electrónico verificado designado de la cuenta (elegido en la configuración del panel, como la parte responsable — no el firmante individual). El correo de notificación directo es solo de entrega y nunca se usa para el hash de revelación.
Cada canal es solo enlace: la notificación lleva la URL de aprobación y nunca el action o payload. La aprobación siempre requiere el passkey vinculado al payload del aprobador en la página de confirmación — no hay aprobar-en-canal.
verifyWebhookSignature recalcula el HMAC-SHA256 hexadecimal del cuerpo de solicitud sin procesar y lo compara en tiempo constante con el encabezado Cosignet-Signature. Pase los bytes exactos que recibió — volver a serializar JSON cambia la firma.
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();
Consulte docs/verification.md para la lista de verificación del lado del llamante: continuar solo con approved, comparar la decisión con la operación que está a punto de ejecutar, y tratar los rechazos/expiración/tiempos de espera como paradas firmes.
new Cosignet({ apiKey, baseUrl?, fetch? })createConfirmation(input) → CreatedConfirmationgetConfirmation(id, { wait? }) → ConfirmationrequestApproval(input, { timeoutMs?, onCreated? }) → ConfirmationverifyWebhookSignature({ body, signature, secret, timestamp?, toleranceSeconds? }) → Promise<boolean>Por defecto, el registro de transparencia retiene el payload y no expone la identidad del aprobador. Establezca public: true en createConfirmation/requestApproval para optar por una sola aprobación en revelación pública: su action + payload sin procesar se vuelven legibles en el paquete de verificación, y un hash PBKDF2 del correo electrónico verificado designado de la cuenta (elegido en la configuración del panel — la parte responsable, no el firmante individual) se compromete en la hoja de Merkle para que cualquiera pueda verificar una dirección candidata contra él.
await cosignet.requestApproval({
username: 'alice',
action: 'Publish Q3 board resolution',
payload: { docId: 'res-2026-Q3' },
public: true, // permanent, irreversible — see caveats below
});
examples/ — ejemplos de cURL, Node, Python, GitLab CI, MCP/tool-wrapper y Worker listos para copiar y pegar.security/ — vinculación de payload, modelo de prueba WebAuthn, modelo de amenazas, registro de transparencia y divulgación responsable.recipes/ai-agent-gate — pausar un agente de codificación de IA (Claude Code) antes de que ejecute un comando de shell peligroso hasta que un humano co-firme.recipes/cli-approval — envolver cualquier comando (por ejemplo, terraform destroy).recipes/ci-cd — bloquear un pipeline (GitHub Actions / GitLab CI) hasta la aprobación.recipes/backend-service — pausar una operación irreversible en un servicio.[email protected]; no publique issues públicos para vulnerabilidades.[email protected].MIT © Cosignet
| Señal | Significado | Su acción |
|---|
status: approved (la firma verifica) | Humano aprobó este payload exacto | Proceder |
status: rejected | Humano rechazó | No ejecutar; informar al solicitante |
status: expired | Nadie decidió a tiempo | No ejecutar; volver a solicitar si aún es necesario |
| Timeout, error de red o 5xx | Estado desconocido | No ejecutar; reintentar con retroceso; alertar tras N fallos |