Credential Vending

Credential vending lets agents request specific fields from stored service credentials without ever holding a plaintext master secret. Bulwark decrypts and returns only the fields needed for a task, only for the duration of the session.

The Problem with Credential Injection

The naive approach to giving agents credentials is to put them in the system prompt or environment variables:

  • The LLM context window contains live secrets
  • Secrets may be logged, traced, or leaked through tool outputs
  • There is no field-level control — the agent gets everything or nothing
  • No audit trail of which fields were actually used

Credential vending solves each of these problems.

How It Works

Agent Session Created
  ↓
Agent calls POST /api/v1/agent/sessions/{id}/credentials
  ↓
Bulwark checks Biscuit token for field-level scopes
  ↓
If approval policy applies → CIBA flow (user approves out-of-band)
  ↓
Bulwark decrypts only requested fields
  ↓
Fields returned to agent for use in this session
  ↓
Grant cached for session lifetime
  ↓
Audit event written

The agent only ever sees the fields it explicitly requested. Other fields in the same service credential — for example a webhook secret when only an API key was requested — are never decrypted.

Field-Level Least Privilege

When a service is registered with Bulwark, each credential field is assigned a scope:

{
  "service_name": "stripe",
  "fields": {
    "secret_key": { "scope": "stripe:secret_key", "sensitive": true },
    "webhook_secret": { "scope": "stripe:webhook_secret", "sensitive": true },
    "publishable_key": { "scope": "stripe:publishable_key", "sensitive": false }
  }
}

An agent's Biscuit token must carry the matching scope to vend a field. A read-only agent can be granted stripe:publishable_key without ever accessing stripe:secret_key.

Approval Policies

Sensitive fields can be protected by an approval policy. When a policy matches a vend request, Bulwark initiates a CIBA out-of-band approval before releasing credentials:

Policy: if field == "secret_key" and agent.trust_level < "high"
  → require human approval

The agent receives 202 approval_required with a poll_url. The user is notified via dashboard notification, email, or push. Once approved, the agent re-submits the request with the approval_id to receive the grant.

Approval policies can match on:

  • Service name or specific field names
  • Agent trust level
  • Time of day or day of week
  • Session context values

TOTP Generation

For services that use TOTP-based two-factor authentication, Bulwark can store the TOTP seed and generate a current code on demand as a special credential field:

{
  "service_name": "legacy-admin-portal",
  "fields": ["totp_code"]
}

Bulwark generates the code at the moment of the vend request using the stored seed. The agent never sees the seed itself.

Grant Reuse Within Sessions

A credential grant is valid for the lifetime of the session (or until the grant's own expires_at, whichever comes first). Subsequent requests for the same service and fields within the same session return the cached grant without triggering another approval or decryption operation.

This means an agent that calls a service multiple times during a task only requests approval once per session. If the task changes scope and needs additional fields, a new vend request is made for those fields specifically.

Audit Trail

Every vend request — whether approved, denied, or approval-pending — is written to the Bulwark audit log with:

| Field | Description | |-------|-------------| | agent_id | The requesting agent | | session_id | The active session | | service_name | The target service | | fields_requested | Which fields were requested | | fields_granted | Which fields were actually returned | | approval_id | CIBA approval ID if applicable | | granted_at | Timestamp | | expires_at | When the grant expires |

Audit events can be streamed to your SIEM via webhooks.