> ## Documentation Index
> Fetch the complete documentation index at: https://docs.codezero.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Routes

> Configure which hosts are intercepted and how credentials are injected.

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`](/cli/route#cordon-route-add) and [`cordon route edit`](/cli/route#cordon-route-edit) over hand-editing routes in `cordon.toml`. The CLI validates hostnames, auth types, headers, secret-source fields, and scoped match fields such as path and method filters. The TOML below is the reference format when you need to inspect, review, or automate outside the CLI.

## Route structure

```toml theme={null}
[[routes]]
name = "stripe"

[routes.match]
host = "api.stripe.com"
path_glob = "/v1/**"
methods = ["POST", "PUT", "DELETE"]

[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). Use `*` inside a hostname label for one-label wildcards or globs, such as `"*.stripe.com"` or `"api-*.stripe.com"`. A wildcard label must be followed by at least two literal labels — `"*.com"` and `"*.app"` are rejected. |
| `match.path_exact` | string           | No            | Exact absolute path to match after percent-decoding and dot-segment normalization. `"/v1/comments"` matches that endpoint only.                                                                                                                                    |
| `match.path_glob`  | string           | No            | Absolute path glob for variable path segments, such as `"/v1/*/comments/**"`. `*` matches exactly one path segment; `**` matches zero or more path segments. For "match everything under this subtree" use `"/v1/**"`.                                             |
| `match.methods`    | array of strings | No            | Uppercase HTTP methods that may receive credentials, such as `["POST", "PUT", "DELETE"]`. Omit to match all methods.                                                                                                                                               |
| `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](/configuration/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](/guides/onepassword#finding-your-account-identifier). |
| `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

### Header auth

Injects a configured header with the secret value. Set `scheme` for `Authorization: Bearer <secret>` style headers, or omit it for raw API-key headers.

```toml theme={null}
[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.

```toml theme={null}
[routes.auth]
type = "basic"
username = "myuser"

[routes.auth.secret]
source = "keyring"
account = "my-basic-cred"
```

### Raw API key header

Injects a custom header with the secret value.

```toml theme={null}
[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 first matches on the **hostname** of the outbound request after canonicalization (case-insensitive, trailing dot ignored).
* Exact host entries match one hostname only. Host entries containing `*` are globs where `*` matches zero or more characters within a single hostname label. For example, `"*.stripe.com"` matches `"api.stripe.com"` but not `"stripe.com"` or `"deep.api.stripe.com"`.
* A configured host match is the operator's trust decision for credential injection, SSRF denylist exemption, and CONNECT interception. To prevent the most common footguns, cordon rejects host globs where a wildcard label is not followed by at least two literal labels: `"*.com"`, `"*.app"`, and `"foo.*"` are rejected; `"*.vercel.app"`, `"api-*.stripe.com"`, and `"*.preview.acme.com"` are accepted.

<Warning>
  This rule does not enforce [public suffix](https://publicsuffix.org/) awareness. Patterns such as `"*.vercel.app"`, `"*.github.io"`, or `"*.herokuapp.com"` pass the check but match subdomains owned by different parties — every Vercel project, GitHub user, or Heroku app under those suffixes. Use the narrowest pattern your workflow allows, such as `"<your-project>-*.vercel.app"`, when you need to scope to deployments you control.
</Warning>

* Configure at most one path matcher: `path_exact` for one endpoint, or `path_glob` for variable segments (use `"/v1/**"` for "match everything under this subtree").
* Configs written by Cordon 0.4.0 may contain `path_prefix`. Cordon still accepts that legacy field. Prefixes that can be represented exactly as globs are rewritten as `path_glob` when the config is saved; other legacy prefixes keep prefix semantics to avoid broadening credential scope.
* If `path_exact` is set, the normalized request path must exactly equal that path.
* If `path_glob` is set, `*` must be a complete path segment and matches exactly one non-empty path segment. `**` must also be a complete path segment and matches zero or more whole path segments. For example, `"/v1/*/comments/**"` matches `"/v1/post/comments"` and `"/v1/post/comments/123"`, but not `"/v1/comments"`, `"/v1/a/b/comments/123"`, or `"/v1/post-*/comments"`.
* Encoded traversal such as `"/v1/%2e%2e/admin"` does not match scoped paths. Encoded path separators such as `"%2F"` or `"%5C"` are treated as non-matches because Cordon forwards the original URI upstream. Paths that would still contain encoded dots or path separators after one decode, such as `"/v1/%252e%252e/admin"`, are also treated as non-matches for credential injection.
* Literal backslashes are also treated as non-matches for scoped paths because some upstream servers interpret them as path separators. Empty path segments are preserved, so `//v1/charges` does not match `/v1/charges`.
* If `methods` is set, the request method must exactly match one of the configured uppercase method tokens.
* On a full route match (host plus any configured path and method filters), cordon strips `Authorization`, `Proxy-Authorization`, and the configured credential header, then injects the managed credential.
* If the host matches but path or method filters do not, the request is not Cordon-managed credential traffic. It passes through without origin-auth stripping or injection.
* Unmatched requests are forwarded without modification.
* Routes are evaluated in order; the first full match wins. Place more-specific routes before broad catch-all routes for the same host. The CLI appends new routes, so reorder `cordon.toml` manually if a broad route was added first.

For HTTPS `CONNECT` traffic, Cordon uses the CONNECT authority to decide whether a configured host should be MITM-intercepted. Path and method filters are evaluated only after the inner HTTPS request is decrypted, so unmatched paths or methods pass through the tunnel without credential stripping or injection.

<Note>
  Configured HTTP route hosts are explicit trust decisions. In v1, requests to a configured route host 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 even when a request does not satisfy optional path or method credential-injection filters. For non-credentialed access to denylist-blocked destinations, see [`denylist_exceptions`](/configuration/overview#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.
</Note>

<Warning>
  Auth stripping is unconditional on full route matches. If your application sends `Authorization: Bearer placeholder` to a request that satisfies host, path, and method filters, cordon removes it and injects the real token. Requests outside those filters are forwarded unchanged.
</Warning>

Route names share one namespace with PostgreSQL listener names and must be unique. See [Listeners](/configuration/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 full route matches and injects the real value from the configured [secret source](/configuration/secret-sources).

| 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](/configuration/secret-sources#rotation-and-restarts).

## Multiple routes

You can configure multiple routes for different APIs:

```toml theme={null}
[[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"
```
