Credential brokering is the whole of ADR-0004 in one sentence:
The credential that hits Obol is never the credential that hits a vendor.
Obol mints its own keys and brokers the customer’s vendor credentials under strict rules. It does not resell pooled vendor accounts, and it does not act as a merchant of record.

The path of one tool call

1

An agent presents an Obol virtual key

Authorization: Bearer ob_live_… on /v1/* or /mcp. The gateway resolves it by SHA-256 hash from the Redis key cache, falling back to control. Revoked, expired, environment-mismatched, and out-of-scope keys are rejected here; a store failure is NotReady, never a pass. See /security/virtual-keys.
2

Policy decides

CEL decides visibility — tools/list never shows a tool the key cannot call. Cedar decides permission, default-deny, with argument conditions. A destructive tool in prod requires an approval unless policy explicitly grants it. All of this is compiled data evaluated in-process, with no network hop per decision. See /policy/overview.
3

The egress guard runs

The resolved upstream URL — an mcp_remote origin or an openapi base URL — is checked before the outbound hop. A blocked upstream terminates the invocation upstream_blocked and is never dispatched. See /security/egress-and-ssrf.
4

The vault unwraps

Vault::unwrap_checked decrypts the connection’s EncryptedCredential inside the gateway process, refusing an expired credential before touching the KEK. An unwrap failure terminates the invocation credential_unavailable; it never falls through to an uncredentialed call. See /security/vault.
5

The credential is injected outbound

OutboundCredential::apply writes the vendor credential onto the outbound request — Authorization: Bearer, Authorization: Basic, a named header with an optional prefix, or a query parameter — with the header value marked sensitive. This is the only place header or query mutation happens.
6

A receipt is written

Every tool call carries an Obol-minted idempotency key (idt_ UUIDv7) and produces a receipt naming the policy id, upstream id, vendor status, and the evidence class of the route. See /receipts/overview.
The plaintext exists between step 4 and step 5 and is dropped immediately after.

What each party can see

Evidence is tier-dependent; the guarantee is not

Everything above — authentication, scoping, CEL visibility, Cedar permission, approvals, budgets, environment separation, revocation at the Obol hop, idempotency — happens upstream of every connector tier and is therefore tier-independent. What Obol can prove afterwards is not. A native route can be gateway_observed; a federated catalog route cannot, because the socket the gateway observed terminated at the broker rather than at the vendor. The receipt always names the class. See /receipts/evidence.

The worker hand-off, and its boundaries

apps/connectors ships nothing — one README, no package, no Dockerfile, no image — and no deployment target defines a connector-worker service or sets OBOL_CONNECTORS_URL. Every native connector with a reviewed pack compiles to the in-process OpenAPI executor instead, which is what makes its transport evidence gateway_observed. The worker:// target and its hand-off are described here because the code path exists in the gateway, not because anything reaches it.
A ToolTarget::Worker call is an MCP tools/call to {OBOL_CONNECTORS_URL}/mcp/{worker} over a per-call streamable-HTTP session (apps/gateway/crates/obol-mcp/src/worker/). Two credentials cross that hop and they do different jobs:
  • Authorization: Bearer <service JWT> — HS256, audience obol-connectors, 60-second TTL, minted per call. This authenticates the gateway, not the customer and not the vendor.
  • x-obol-upstream-authorization — the rendered vendor credential, present only when the connection has one. It is produced by running OutboundCredential::apply into a scratch header map, marked sensitive, and passed as a custom header. The worker copies it onto its vendor request and never persists it.
The body is the frozen WorkerCallRequest contract: {connection_id, workspace_id, tool, arguments, idempotency_key}. ADR-0014 chose a header over the body deliberately — bodies get logged, buffered, and snapshotted by frameworks far more often than headers, and MCP handlers see bodies but not headers. Hard boundaries on that header:
  • It is never logged. The only log line for a worker call carries the worker, connection, tool, idempotency key, and last4. A gateway test asserts the string never appears in log output, and another asserts the header name is mentioned nowhere in the tree outside obol-mcp/src/worker.
  • It never appears on a remote MCP relay call — a relay test asserts its absence.
  • A worker holds no key material at rest and has no KEK or KMS dependency.
  • Retries are bounded by transport certainty and the tool’s declared vendor idempotency binding, and reuse the same idempotency key. A post-send transport failure is never retried, because the worker may have executed.

Why workers are meant to be credential-blind

ADR-0024 decided the destination state and is accepted-not-implemented (parked by ADR-0058). It states the rule plainly:
ADR-0024’s rule — not yet the code. ADR-0024 requires that a worker never see a vendor credential and never call a vendor, making trusted workers request planners and response normalizers while the gateway performs all credentialed vendor HTTP. That protocol is not implemented. The shipped gateway still runs ADR-0014’s hand-off and would pass the credential to the worker. Nothing crosses that hop today, because no worker exists to receive it, but do not cite this rule as a property the system currently has.
The reasoning is that redaction and no-at-rest rules reduce accidental leakage but do not stop a compromised worker dependency from exfiltrating each live credential. Under ADR-0024 a worker would return a RequestPlan — a declared operation id, method, relative path, selected safe headers, and a bounded body, never an absolute destination and never a credential — which the gateway would validate against the pinned connector bundle before injecting the vaulted credential and performing the HTTP itself. Worker-derived semantic fields would stay connector_attested; HTTP status, digests, and gateway-selected response fields would stay gateway_observed. A worker cannot assign trust, evaluate Cedar, mint a receipt, or choose a verification conclusion. ADR-0024 supersedes ADR-0014’s credential hand-off and retry mechanics on paper. The shipped gateway path is still ADR-0014’s x-obol-upstream-authorization header, so treat that header as legacy surface: do not extend it, and do not add a worker that depends on it. A vendor whose request signing Obol cannot express as an auth profile is a reviewed exception requiring a new ADR, not a secret hand-off. See /connectors/trusted-workers for the tier decision and what a worker is for.

What is prohibited outright

  • Pooled vendor accounts resold through Obol’s API.
  • Storing a customer’s full sk_live_. Control rejects any sealable secret beginning with sk_live_, and the gateway rejects a Stripe API-key seal beginning with sk_live_ again on its own side.
  • A shared broker OAuth application across tenants. A federated connection uses the customer’s own OAuth client, and activation fails if one broker account identifier appears under more than one workspace_id (ADR-0030).
  • Exposing a vendor secret to an agent, a worker config, a log, or the web app.
  • Pass-through, where the agent holds the vendor key and Obol only logs.
See /concepts/invariants for the full list.