ADR 0008: Credential broker
Short-lived scoped leases issued to the tool runtime after every refusal — never to the agent, and never early.
#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 and
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.
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