CIBA: Human-in-the-Loop Authorization

Client Initiated Backchannel Authentication (CIBA) lets an AI agent pause and request explicit human approval before taking a sensitive action.

The Problem

Autonomous agents are powerful but risky. When an agent can:

  • Send an email to a customer
  • Execute a payment
  • Delete records
  • Post publicly on behalf of a user

...you need a way for a human to approve or deny the action before it happens.

How CIBA Works

Agent                  Bulwark               User (mobile/email)
  |                       |                          |
  |-- POST /ciba/authorize→|                          |
  |                       |-- notify user ---------->|
  |<- authReqId ----------|                          |
  |                       |                          |
  | (polling loop)        |          (user reviews)  |
  |-- GET /ciba/token --->|                          |
  |<- { status: pending } |                          |
  |                       |                          |
  |                       |<-- POST /ciba/approve ---|
  |                       |                          |
  |-- GET /ciba/token --->|                          |
  |<- { status: approved, |                          |
  |     accessToken: ... }|                          |
  |                       |                          |
  | (execute action)      |                          |

Implementation

1. Request approval

const { authReqId, expiresIn } = await bulwark.ciba.authorize({
  userId: "usr_01j...",
  scope: "approve:payment",
  bindingMessage: "Approve payment of $500 to Acme Corp?",
  requestedExpiry: 300, // 5 minutes
  context: {
    amount: 500,
    recipient: "Acme Corp",
    currency: "USD",
  },
});

2. Poll for result

let approval;
while (true) {
  approval = await bulwark.ciba.poll(authReqId);

  if (approval.status === "approved") break;
  if (approval.status === "denied") throw new Error("User denied");
  if (approval.status === "expired") throw new Error("Approval expired");

  await new Promise((r) => setTimeout(r, 5000)); // poll every 5s
}

// Proceed with the approved action
await executePayment(approval.accessToken);

3. User approves (dashboard/mobile)

The Bulwark dashboard and mobile app surface pending CIBA requests to users. Your app can also build a custom approval UI using the CIBA endpoints.

// In your approval UI
await bulwark.ciba.approve(authReqId);
// or
await bulwark.ciba.deny(authReqId);

LangChain Integration

import { CIBAApprovalTool } from "@bulwark/langchain";

const sendPaymentTool = new CIBAApprovalTool({
  toolkit,
  name: "send_payment",
  description: "Send a payment (requires human approval)",
  approvalScope: "approve:payment",
  buildMessage: ({ amount, recipient }) =>
    `Approve payment of $${amount} to ${recipient}?`,
  onApproved: async ({ amount, recipient }) => {
    // executed only after human approves
    return await paymentGateway.send({ amount, recipient });
  },
});

Vercel AI SDK Integration

import { createCIBATool } from "@bulwark/vercel-ai";

const tool = createCIBATool({
  session,
  userId: "usr_01j...",
  name: "delete_record",
  description: "Delete a customer record — requires human approval",
  approvalScope: "approve:delete",
  parameters: z.object({ customerId: z.string() }),
  buildMessage: ({ customerId }) =>
    `Permanently delete customer ${customerId}? This cannot be undone.`,
  onApproved: ({ customerId }) => db.customers.delete(customerId),
});

When to Require CIBA

Use CIBA for any agent action that:

  • Is irreversible (delete, publish, send)
  • Involves money or sensitive data
  • Acts on behalf of a user in the outside world
  • Has significant business impact

Configure trust-level-based CIBA requirements in the Bulwark dashboard — critical trust agents always require CIBA for defined action categories.