Routes define which outbound requests cordon intercepts and what credentials to inject. Each route matches on a destination hostname and specifies an auth type.
Prefer cordon route add and cordon route edit over hand-editing routes in cordon.toml. The CLI validates hostnames, auth types, headers, and secret-source fields, and is meant as the safer way to produce this configuration. The TOML below is the reference format when you need to inspect, review, or automate outside the CLI.
Route structure
[[routes]]
name = "stripe"
[routes.match]
host = "api.stripe.com"
[routes.auth]
type = "header"
header_name = "Authorization"
scheme = "Bearer"
[routes.auth.secret]
source = "1password"
account = "my-team"
vault = "Engineering"
item = "Stripe API Key"
field = "secret_key"
| Field | Type | Required | Description |
|---|
name | string | Yes | Identifier for the route (used in logs). |
match.host | string | Yes | Hostname to match (case-insensitive). |
auth.type | string | Yes | header or basic. |
auth.username | string | basic only | Username for HTTP Basic authentication. |
auth.header_name | string | header only | Header name for header auth. |
auth.scheme | string | header only | Optional HTTP auth scheme. Required when auth.header_name is Authorization. |
auth.secret | object | Yes | Secret source reference. See Secret Sources. |
When auth.secret.source is "1password", the following fields are available:
| Field | Type | Required | Description |
|---|
auth.secret.source | string | Yes | "1password" |
auth.secret.account | string | No | 1Password account identifier — sign-in address, shorthand, account UUID, or user UUID. Recommended for multi-account and service setups. See the 1Password guide. |
auth.secret.vault | string | Yes | Vault name within the account. |
auth.secret.item | string | Yes | Item name within the vault. |
auth.secret.field | string | Yes | Field label within the item (case-sensitive). |
Auth types
Injects a configured header with the secret value. Set scheme for Authorization: Bearer <secret> style headers, or omit it for raw API-key headers.
[routes.auth]
type = "header"
header_name = "Authorization"
scheme = "Bearer"
[routes.auth.secret]
source = "1password"
account = "my-team"
vault = "Engineering"
item = "Stripe API Key"
field = "secret_key"
Basic auth
Injects an Authorization: Basic <base64(username:secret)> header.
[routes.auth]
type = "basic"
username = "myuser"
[routes.auth.secret]
source = "keyring"
account = "my-basic-cred"
Injects a custom header with the secret value.
[routes.auth]
type = "header"
header_name = "X-Api-Key"
[routes.auth.secret]
source = "keyring"
account = "example-api-key"
header_name must be a valid HTTP field name and cannot be one of Cordon’s reserved credential injection headers: Host, Content-Length, Transfer-Encoding, Connection, Keep-Alive, TE, Trailer, Upgrade, Proxy-Authorization, or Proxy-Authenticate.
How matching works
- Cordon matches on the exact hostname of the outbound request after canonicalization (case-insensitive, trailing dot ignored). Wildcards and subdomains are not matched automatically.
- For matched routes, cordon strips
Authorization, Proxy-Authorization, and the configured credential header before injection.
- Unmatched requests are forwarded without modification
- Routes are evaluated in order; the first match wins
Configured HTTP routes are explicit trust decisions. In v1, matched routes bypass private/link-local/loopback SSRF denylist checks so internal APIs, VPN/private endpoints, PrivateLink services, staging environments, and localhost development services continue to work. For non-credentialed access to denylist-blocked destinations, see denylist_exceptions instead. DNS pinning still applies, but DNS pinning is not private-IP blocking. Routes authorize credential injection and upstream selection for the configured host; they do not protect against malicious same-user callers. Post-v1 private-upstream policy work will revisit this default.
Auth stripping is unconditional for matched routes. If your application sends Authorization: Bearer placeholder, cordon removes it and injects the real token. This is by design — it prevents accidental credential leakage if the application has a real token.
Route names share one namespace with PostgreSQL listener names and must be unique. See Listeners for PostgreSQL listener configuration.
Provider auth quick reference
Use the auth type expected by the upstream API, even if your app or agent only sends a dummy credential. Cordon strips any inbound credential on matched routes and injects the real value from the configured secret source.
All providers below use type = "header". The table shows the specific header_name and scheme to use for each.
| Provider / API | Host | header_name | scheme | Notes |
|---|
| Anthropic | api.anthropic.com | x-api-key | (omit) | Do not set scheme. Using Authorization: Bearer will cause 401s. |
| OpenAI | api.openai.com | Authorization | Bearer | Agents may need a dummy API key so they choose API-key auth instead of OAuth. |
| GitHub REST | api.github.com | Authorization | Bearer | Can read the gh token from the keyring with a custom service. |
| Stripe | api.stripe.com | Authorization | Bearer | Stripe API keys are sent as bearer tokens. |
If an application needs a provider-specific env var to select an auth path, set a placeholder such as dummy-replaced-by-cordon. The placeholder is not trusted: Cordon removes it and injects the real credential.
Route changes and secret rotation
Cordon loads route definitions when the proxy starts. Restart Cordon after adding or editing routes. HTTP route secrets are fetched per request, so rotating the secret value in 1Password or the keyring does not require a restart. PostgreSQL listener secrets are different; see Secret Sources.
Multiple routes
You can configure multiple routes for different APIs:
[[routes]]
name = "anthropic"
[routes.match]
host = "api.anthropic.com"
[routes.auth]
type = "header"
header_name = "x-api-key"
[routes.auth.secret]
source = "1password"
account = "my-team"
vault = "Engineering"
item = "Anthropic API Key"
field = "credential"
[[routes]]
name = "openai"
[routes.match]
host = "api.openai.com"
[routes.auth]
type = "header"
header_name = "Authorization"
scheme = "Bearer"
[routes.auth.secret]
source = "1password"
account = "my-team"
vault = "Engineering"
item = "OpenAI API Key"
field = "credential"
[[routes]]
name = "stripe"
[routes.match]
host = "api.stripe.com"
[routes.auth]
type = "header"
header_name = "Authorization"
scheme = "Bearer"
[routes.auth.secret]
source = "1password"
account = "my-team"
vault = "Engineering"
item = "Stripe API Key"
field = "secret_key"