Skip to content
route402.dev
Documentation
read as markdown

Upstream credentials

How your API key is stored, injected, and why nothing can read it back.

route402 holds third-party API keys. That makes the credential vault the most security-sensitive thing here, so the rules are strict and few.

Write-only, everywhere

Once stored, a credential cannot be read back:

  • No MCP tool returns it. set_upstream_auth answers with a keyed digest and nothing else.
  • The console does not display it, not even masked. There is no "reveal" button because there is no read path to put behind one.
  • No API endpoint returns it. The single internal endpoint that hands the wrapped blob to the gateway is authenticated with a secret derived from VAULT_MASTER_KEY, which only the gateway holds.

An agent that can read back a customer's API key is a breach waiting to happen. The safest way to build that is to never build the read path.

If you lose the credential, you cannot recover it from here. Get a new one from your upstream and save it again.

How it is stored

Envelope encryption. Each credential gets a fresh 256-bit data key; the data key is itself encrypted under a key derived from VAULT_MASTER_KEY.

credential --AES-256-GCM--> ciphertext
data key   --AES-256-GCM--> wrapped data key   (under the master key)

Rotating the master key re-wraps data keys without touching the ciphertexts. Losing it loses every stored credential — there is no backdoor.

How it is injected

At call time the gateway decrypts in memory, adds the credential to the outbound request, and discards it. It is never logged, never included in an error message, and never returned in a response.

Three placements are supported:

Placement What the upstream receives
bearer Authorization: Bearer
header :
query ?= appended to the URL

Egress control

A publisher supplies an arbitrary upstream URL and we then make requests to it. Without a guard, "wrap my API" becomes "fetch http://169.254.169.254/ for me".

So every upstream is checked at creation and again at call time, because DNS can change in between:

  • http:// and https:// only.
  • The hostname must resolve, and every address it resolves to must be public. A record that returns one public and one private address is still a pivot.
  • Private, loopback, link-local, CGNAT and multicast ranges are refused, as are localhost and .internal names.
  • Credentials embedded in the URL are refused — store them properly instead.
  • Redirects are not followed, because a redirect can leave the checked host.

EGRESS_BLOCK_PRIVATE_RANGES=false disables the resolution checks. It exists for local development against a service on your own machine. Do not set it in production.

Audit

Every write, rotation and removal is recorded with the actor that performed it — a console session or a specific MCP client id.

NextDirectory and trust signals