---
title: 'ADR 0008: Credential broker'
description: Short-lived scoped leases issued to the tool runtime after every refusal — never to the agent, and never early.
label: '0008 Credential broker'
---

## Context

An agent that calls your refund API needs to authenticate to it. The
conventional answer is a long-lived API key in the agent's environment.

For an agent specifically, that is a worse position than for a service. The key
is in a process whose job is to produce text from a model, whose context can be
influenced by whatever it reads, and whose output is often logged. Prompt
injection becomes credential theft, and the blast radius is whatever that key
can do — which is usually everything, for as long as the key lives.

## Decision

**The agent never holds the credential.**

The agent asks Veefore to perform an action. Veefore decides. Only after the
action is permitted does the broker issue a **lease** — a short-lived credential
scoped to that action — **to the tool runtime**, inside the gateway process.

```text
 1–9   everything that can refuse
10     CREDENTIAL   ← unseal, lease, record by reference
11     EXECUTION    ← inject into the outbound request
12     RESULT
```

**Stage 10, after everything that can refuse.** This position is the decision.

**Material is sealed on ingest** with `VEEFORE_DATA_ENCRYPTION_KEY`, in a column
the repository layer does not select into API responses. **No endpoint returns
it.**

**Leases are recorded by reference**, never by value. Default lifetime 600
seconds. Statuses `ISSUED`, `USED`, `EXPIRED`, `REVOKED`.

**Rotation invalidates every outstanding lease.**

**Seven credential types**, each reading specific keys from `material` —
validated at registration and at rotation.

**Pluggable provider.** `local` seals in PostgreSQL; production deployments
point it at their own secret manager.

## Alternatives

**Give the agent a long-lived key.** The industry default. Everything above.

**Give the agent a short-lived token it requests itself.** Better than a
long-lived key, and the agent still holds a usable credential — so prompt
injection still yields something, just something with a shorter life. It also
means the agent can obtain a credential without performing an action, which
decouples "may I act" from "give me the means to act".

**Broker to the agent rather than to the runtime.** The agent would make the
downstream call itself with a leased credential. Lower latency and one fewer hop
through the gateway.

It also means the credential enters the agent's process, which is the thing this
ADR exists to prevent — and it means the agent could make a *different* call
with the lease than the one that was authorized. The scoping would be advisory.

**Broker in parallel with policy evaluation, for latency.** Saves a few
milliseconds on the allowed path. It also means a blocked action had already
caused a credential to be issued, so "we blocked it" and "the secret did not go
out" would become two different claims with only one of them true.

That is the single most important consequence of the stage order, and it is why
the broker is not an optimisation target.

**No broker at all — require customers to use a secret manager and let the tool
runtime read from it directly.** Simpler, and it removes the sealing
responsibility. It also removes the lease: there is no per-action scoping, no
record of what was issued for which decision, and no single place to revoke.

## Consequences

**Every protected action that needs a credential costs an unseal.** Measured in
`veefore_credential_leases_total` and
`veefore_credential_broker_errors_total`. Acceptable within the pipeline's
20–50 ms budget.

**`VEEFORE_DATA_ENCRYPTION_KEY` becomes load-bearing.** Lose it and every
credential is unreadable. It must be backed up with the database and injected
from a secret manager, and a database dump alone is **not a restorable backup**
— stated prominently in [Database](/operate/database) and
[Upgrades](/operate/upgrades).

Rotating it requires re-sealing every credential. A migration, not a config
change.

**Material-key mistakes used to fail two layers away.** Putting the secret under
a name the broker does not read for that type meant Veefore sealed it happily,
permitted the action happily, brokered nothing usable, and the downstream
returned `401` — with nothing in the Veefore logs looking wrong, because from
Veefore's point of view nothing was.

Now refused at registration and at rotation with
`INVALID_CREDENTIAL_REFERENCE`. `BASIC_AUTH`, `MTLS_CERTIFICATE` and
`SIGNING_KEY` are exempt because their material is legitimately multi-field.

This is the clearest example of the fail-closed instinct applied to something
that was not a dependency outage: it previously failed *late*, which is the same
problem as failing open.

**Migration requires removing the key from the agent.** Registering the
credential and attaching it to a tool achieves nothing while the agent still has
the key in its environment. That step is the one that is easy to skip and the
only one that actually improves anything.

**The SDK's non-load-bearing claim depends on this.** An agent that bypasses the
SDK has nothing to call with — but only because the credential is absent. For an
agent that still holds a key, the claim does not hold. Stated explicitly in
[The boundary](/secure/boundary).

**A leaked lease is bounded, and the bound is specific.** Short-lived, scoped to
one action, and the action records say exactly which leases were issued during
any window. "What could that key do" becomes "what could those leases do", which
is a much smaller question. [→ Credential leak](/operate/runbook-credential-leak)

## Related

- [Credential broker](/concepts/credentials)
- [Broker a credential](/build/broker-a-credential)
- [Secrets](/secure/secrets)
- [Gateway architecture](/architecture/adr/0002-gateway-architecture)
