Credential Broker
When an agent calls an MCP target through Behavry, the target's credential is injected at dispatch by Behavry. The agent never holds it.
This page states that position precisely, including where it does not hold — because a custody claim that is true for most of your traffic is worse than no claim at all. The customer finds the gap during diligence instead of hearing it from you.
The accurate claim
For every MCP target with registered auth, the agent holds no downstream credential, and the target sees Behavry.
That is narrower than "agents hold credentials to Behavry only, and every downstream credential is held in a KMS-backed broker." Three things are wrong with the broader version, and all three are verifiable in the product:
- The LLM proxy routes require the agent to hold the key. The Anthropic, OpenAI, and Gemini proxy routes each read a caller-supplied key header and reject the request without one. For that traffic the agent holds the credential and Behavry passes it through.
- "Every downstream credential" overclaims. An MCP target registered without auth headers has no credential at all. There is nothing being brokered.
- "KMS-backed" misleads on cloud deployments. MCP credential encryption uses a local AES-256-GCM envelope keyed by
BEHAVRY_LOCAL_ENCRYPTION_KEY— including on a plane whose Data Protection pipeline runs against AWS KMS. Those are separate paths.
BEHAVRY_LOCAL_ENCRYPTION_KEY is unsetEncryption is skipped entirely and the credential lives only in the process. That state is reported as brokered_in_memory, never as encrypted at rest. See Environment variables.
The four custody states
The custody surface reports a state per target rather than a single reassuring number:
| State | Meaning |
|---|---|
brokered_encrypted | Behavry holds the credential, encrypted at rest. This is the real claim. |
brokered_in_memory | Behavry holds it, but it is not encrypted at rest. |
no_credential | Nothing is configured. A gap worth an operator's attention, not a silent success. |
agent_supplied | The agent holds its own key — the LLM proxy routes. |
The caveats render at the same visual weight as the reassurance. That is a deliberate design choice, not an oversight.
The custody surface never decrypts
It reports three things and nothing else:
- Presence — whether a credential exists
- A non-reversible reference —
sha256(ciphertext)truncated to 16 characters - The encryption-context label — which provider actually encrypted it
An assurance surface that had to decrypt a secret in order to describe it would be one mishandled log line away from leaking the thing it exists to reassure you about. This one is structurally incapable of it.
The reference is still useful: it changes on rotation, and it joins a call to a target.
Per-call attestation
When a credential is genuinely injected, a credential_brokered marker rides the existing decision event's extra field rather than being written as a separate audit row. It inherits that event's signature and hash-chain position for free.
The marker is absent when nothing was brokered — it does not simply record that the code ran. It names the credential reference and the policy decision that authorised the injection, and never the secret.
Evidence packages
APR evidence packages carry a credential_custody.json component aggregating brokered calls per credential reference, with its own scope_note stating the pass-through exception — because the artifact outlives the UI that explained it.
It is additive: the manifest signs only itself, so this cannot invalidate an existing package and needs no verifier change. See Public Evidence Verification.
Reading it
| Surface | What it gives you |
|---|---|
GET /api/v1/agents/{id}/creds | Custody counts, and the per-target list |
behavry agent creds <id> | The same, from the CLI |
| Dashboard assurance panel | The operator view |
Two privilege tiers on one endpoint
The response mixes per-agent counts with tenant connector inventory — target names, transports, credential references, and which targets are unauthenticated.
Admin privileges exist so an administrator can hold agents without holding connectors. Gating the whole endpoint on agents-read would have let an agents-read caller enumerate connector inventory through any readable agent.
So:
- The counts need agents-read.
- The per-target list additionally needs connectors-read.
Its absence is reported through target_detail_visible rather than looking like an agent with no targets.
What operators get out of it
Two answers that were previously unavailable:
- Which of my targets are unauthenticated?
- Which agents still hold their own API keys?
Nothing about enforcement changes. This surface is additive and read-only.
Related
- Agent Identity — how an agent authenticates to Behavry
- Data Protection — the separate KMS-backed pipeline
- Connectors — registering targets and their auth