Securing AI gateway credentials: storing API keys, provider credentials, and service tokens centrally, secure integration with external toolchains, as well as rotation and least-privilege access strategies.

In episode 10 you made routing changes safely rollbackable. But there's one class of secrets that must never enter version control at all: credentials. One leaked API key in a commit means the entire gateway can be abused — and the LLM bill flows to the attacker.
Episode 11 closes the foundational security side: secrets and integration security. We'll cover why provider credentials must not be placed raw in config, how to store API keys and service tokens centrally, referencing secrets from config without writing them, secure integration with external toolchains, and rotation and least-privilege access strategies.
Many people stop at "store it in an environment variable". That's a good start, but not the end. Env vars hardcoded in a Dockerfile or compose file are still plaintext that can leak through images, logs, or container snapshots. Credentials deserve their own class of treatment: encrypted at rest, rotated regularly, and granted minimal access rights.
There are three kinds of secrets a gateway often holds: LLM provider API keys (OpenAI, Anthropic, Azure), service tokens for authenticating to external toolchains, and encryption keys for internal data. All three have different lifecycles and risks — treat each as an independent secret, not one secret block.
Warning
The first rule: secrets never enter the repo, the image, or the logs. If a single secret leaks, the entire chain of integrations using it is at risk — not just one provider.
9router doesn't store credentials in config files; it reads them from a secret manager. The simplest option to start is a cloud secret manager (AWS Secrets Manager, GCP Secret Manager, or HashiCorp Vault), then reference them by ID, not value. This separates "what the config is" from "what the secret is".
secrets:
backend: vault
address: https://vault.internal:8200
providers:
openai: vault:kv/9router/openai#api_key
anthropic: vault:kv/9router/anthropic#api_key
azure: vault:kv/9router/azure#api_key
service_tokens:
slack: vault:kv/9router/tools/slack#tokenThe gateway loads secret values from Vault at startup and keeps them only in memory, never on disk or in config. Every access to a secret can be audited by the secret manager, so you know who pulled which key and when. Make sure the gateway's connection to Vault uses its own authentication that's stronger than a static token.
The config stays declarative and clean of secret values. The reference template looks like vault:path#field — you only mention the location, not the content. When deploying to many environments, just change the backend or path, without ever touching the secret values.
npx @9router/cli secrets check{
"providers": { "openai": "resolved", "anthropic": "resolved", "azure": "missing" },
"service_tokens": { "slack": "resolved" },
"recent_rotation": { "openai": "21 days ago" }
}The npx @9router/cli secrets check command validates that all references can be resolved without ever printing the values. This closes the classic gap: a config that passes structural validation but fails at runtime because of a wrong secret path. Run this check together with validation in CI, as in episode 10.
The 9router gateway often calls external toolchains — databases, CRMs, message brokers — using service tokens. Secure integration means three things: tokens stored centrally, connections encrypted in transit, and token access rights scoped. For mTLS or key-based auth, store the key material in the secret manager too.
integrations:
- name: crm
type: api
url: https://crm.internal/api
auth: token
secret: vault:kv/9router/tools/crm#token
tls:
min_version: "1.2"
allowed_scopes: [crm:read]Notice allowed_scopes is only crm:read. This token can't write, delete, or access other resources — the damage possible if the token leaks is also limited. Always encrypt connections with TLS and make sure no service token slips through to an LLM provider as request context.
Info
The zero-trust principle also applies in reverse: don't give external toolchains access to your gateway just because the gateway calls them. Every integration uses its own identity that can be revoked.
Credentials must change periodically because time increases the chance of leakage. Good rotation is automatic and two-phase: the new key is used first before the old key is removed, so there's never a moment of service outage. 9router supports multiple credentials per provider for a smooth transition.
providers:
openai:
credentials:
- id: active
secret: vault:kv/9router/openai#key_v2
- id: previous
secret: vault:kv/9router/openai#key_v1
status: drainingWhen key_v1 enters the draining status, the gateway uses key_v2 for new requests and finishes old requests with key_v1 until it's empty, then key_v1 is deleted. This cycle can also be triggered from the CLI: 9router secrets rotate --provider openai swaps the active and previous statuses automatically, so the drain-and-delete flow runs without manual config edits.
Pair that with least privilege: each secret only has the scope its specific function needs — an analytics token can't write models, a staging key can't access prod. Schedule rotation as a recurring routine, say every 90 days, and tie it to the secret manager audit so no key idles without a reminder. Revoke secrets that are no longer used — old, unrotated, undeleted secrets are just waiting to leak.
Secrets and integration security ties together everything you've learned: credentials are pulled out of config and stored centrally, config only holds references, external toolchains are accessed with limited-scope tokens over encrypted connections, and two-phase rotation keeps the service alive while cleaning up old keys. Least privilege is the final filter — every secret may only do what it's actually for.
Key takeaways:
vault:path#field, never the secret values.draining status enables key changes without downtime.Episode 12 opens Phase 4, the broader security: Secure Gateway & Access Control — authenticating incoming requests, authorizing route and tool invocation access, up to transport security and encrypted provider connections. See you there!