Skip to main content

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:

  1. 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.
  2. "Every downstream credential" overclaims. An MCP target registered without auth headers has no credential at all. There is nothing being brokered.
  3. "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.
If BEHAVRY_LOCAL_ENCRYPTION_KEY is unset

Encryption 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:

StateMeaning
brokered_encryptedBehavry holds the credential, encrypted at rest. This is the real claim.
brokered_in_memoryBehavry holds it, but it is not encrypted at rest.
no_credentialNothing is configured. A gap worth an operator's attention, not a silent success.
agent_suppliedThe 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 referencesha256(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.

Its presence means an injection really happened

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

SurfaceWhat it gives you
GET /api/v1/agents/{id}/credsCustody counts, and the per-target list
behavry agent creds <id>The same, from the CLI
Dashboard assurance panelThe 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.