---
title: Credential broker
description: The agent never holds the secret. Veefore seals material on the way in and leases short-lived, scoped credentials on the way out.
label: Credential broker
---

An agent that holds an API key is an agent whose API key is in its prompt
context, in its logs, and in whatever the model decides to say next. Prompt
injection becomes credential theft, and the blast radius is whatever that key
can do.

The credential broker removes the key from the agent entirely. The agent asks
Veefore to perform an action. Veefore decides. Only after the action is permitted
does the broker issue a short-lived, scoped credential — to the **tool runtime**,
not to the agent.

:::security The agent never sees it
A leaked prompt leaks nothing usable. A compromised agent can only ask for
actions, and every ask runs the full pipeline. There is no key to exfiltrate,
because the agent was never given one.

This is also what makes the SDK genuinely non-load-bearing: an agent that
bypasses the SDK and calls your API directly has no credential to call it with.
:::

:::diagram broker | The credential reaches the tool runtime inside the gateway. It never reaches the agent.
The agent holds only its own identity key. The gateway runs stages one to nine,
and only after the action is permitted does the broker unseal the stored
material and lease it — scoped to that one action — to the tool runtime. An
agent calling your service directly has no credential to call with, which is
what makes the SDK genuinely non-load-bearing.
:::

## The flow

1. You register a credential **reference** with its material. Veefore seals the material with `VEEFORE_DATA_ENCRYPTION_KEY`.
2. You attach the credential to a tool.
3. An agent requests an action on that tool.
4. The pipeline runs. Stage 10 is the first stage with an external side effect — every refusal has already happened.
5. The broker issues a **lease**: a short-lived credential, scoped to this action.
6. The tool runtime uses it. The lease is recorded by reference and expires.

Default lease lifetime is `CREDENTIAL_DEFAULT_TTL_SECONDS`, 600 seconds. Lease
statuses are `ISSUED`, `USED`, `EXPIRED`, `REVOKED`.

## Registering one

```bash
curl -X POST https://veefore.example.com/v1/credentials \
  -H "Authorization: Bearer $VEEFORE_API_KEY" \
  -H 'content-type: application/json' \
  -d '{
    "key": "payments-api-key",
    "name": "Payments service API key",
    "type": "API_KEY",
    "provider": "local",
    "material": {
      "api_key": "…",
      "header": "x-api-key"
    }
  }'
```

Supported types: `API_KEY`, `BEARER_TOKEN`, `BASIC_AUTH`, `DATABASE_DSN`,
`OAUTH_CLIENT`, `MTLS_CERTIFICATE`, `SIGNING_KEY`.

:::danger Use the right key names in `material`
Each credential type reads specific keys and discards the rest. `API_KEY`
accepts `api_key`, `apiKey`, `key` or `value`, plus optional `header` and
`prefix`. Put the secret under a name the broker does not read and you get a
failure two layers away from the cause: Veefore seals and stores it happily,
permits the action happily, brokers nothing, and your downstream returns `401`.

Veefore now refuses this at registration and at rotation with
`INVALID_CREDENTIAL_REFERENCE` rather than letting you find out in production.
`BASIC_AUTH`, `MTLS_CERTIFICATE` and `SIGNING_KEY` are exempt from the check
because their material is legitimately multi-field.
:::

| Type | Reads |
| --- | --- |
| `API_KEY` | `api_key` / `apiKey` / `key` / `value`, plus `header`, `prefix` |
| `BEARER_TOKEN` | `token` / `access_token` / `value` |
| `BASIC_AUTH` | `username` and `password` |
| `DATABASE_DSN` | `dsn` / `url` / `connection_string` |
| `OAUTH_CLIENT` | `client_id` and `client_secret` |
| `MTLS_CERTIFICATE` | `certificate` and `private_key` |
| `SIGNING_KEY` | `private_key` and `algorithm` |

## Secrets never come back out

No endpoint returns credential material. `GET /v1/credentials` returns metadata
only: key, name, type, provider, status, created, last rotated, lease count.

The guarantee is enforced in several places at once, deliberately:

- The repository layer does not select the sealed column into API responses.
- Log redaction strips credential-shaped values from every log line.
- Audit events record the credential by id, never by value.
- `npm run quality:audit` fails the build on a committed secret.
- The CLI smoke test asserts no command prints a secret.

If you lose the material, you rotate. You do not read it back. That is the
intended one-way door.

## Rotation

```bash
curl -X POST https://veefore.example.com/v1/credentials/cred_01ABC/rotate \
  -H "Authorization: Bearer $VEEFORE_API_KEY" \
  -H 'content-type: application/json' \
  -d '{ "material": { "api_key": "…", "header": "x-api-key" } }'
```

Rotation seals the new material and **invalidates every outstanding lease**. An
in-flight action holding an old lease fails rather than continuing with a
credential you have decided to retire.

`credential:rotate` requires step-up authentication. The status moves through
`ROTATING` while it happens, so the transition is observable.

Rotate on a schedule. `veefore doctor` reports credentials that are overdue, and
`CREDENTIAL_ANOMALY` is a risk signal that fires on unusual credential use.

## Revocation

```bash
curl -X POST https://veefore.example.com/v1/credentials/cred_01ABC/revoke \
  -H "Authorization: Bearer $VEEFORE_API_KEY" \
  -H 'content-type: application/json' \
  -d '{ "reason": "Key published in a support ticket" }'
```

Immediate. Every lease dies, and every tool depending on the credential starts
failing at stage 10 with `INVALID_CREDENTIAL_REFERENCE`. `credential:revoke` is
high impact and step-up gated.

## Providers

`CREDENTIAL_BROKER_PROVIDER` selects the backend. `local` seals material in
PostgreSQL with `VEEFORE_DATA_ENCRYPTION_KEY`, which is correct for development
and for deployments where Veefore's database is already your trust boundary.

For production, point it at your existing secret manager so the material lives
where the rest of your secrets live and inherits that system's rotation,
auditing and access control. The broker's behaviour is identical either way.

:::warning Rotating the data encryption key needs re-sealing
`VEEFORE_DATA_ENCRYPTION_KEY` is what seals stored material under the `local`
provider. Changing it without re-sealing makes every stored credential
unreadable, and the symptom is every tool failing at once. Plan it as a
migration, not a config change.
:::

## Why stage 10 and not earlier

Brokering is the first irreversible step. Everything before it — authentication,
identity, lifecycle, capability, delegation, policy, risk, approval — can refuse
with no trace outside Veefore.

Putting the broker after all of them is what makes `BLOCK` mean blocked.
Brokering earlier, say for performance, would mean a blocked action had already
caused a credential to be issued. Then "we blocked it" and "the secret did not
go out" would be two different claims, and only one of them would be true.

## Related

:::cards

:::card Broker a credential | /build/broker-a-credential
Working setup, including the real-egress guards.
:::

:::card Secrets | /secure/secrets
Every place a secret could appear, and what stops it.
:::

:::card Register a tool | /build/register-a-tool
Attaching a credential to a tool.
:::

:::card Credentials API | /reference/api/credentials
Register, rotate, revoke, read metadata.
:::

:::
