Resolution Modes
Direct vs short-lived resolution modes, the credential-swapping proxy, and when to use each
Each resource has a mode that controls how the secret reaches the caller. keyzero supports two modes: direct and short_lived.
Direct Mode
The secret value is fetched from the backend and returned in the API response.
resources:
- ref: "secret/data/prod/db/**"
resolver: vault-prod
mode: direct
Response:
{
"results": {
"secret/data/prod/db/password": {
"mode": "direct",
"value": "the-actual-secret"
}
}
}
How it works:
- Policy allows the request
- keyzero fetches the secret from the backend
- The raw value is returned to the caller
Trade-off: Simple, but the caller sees the raw secret. Suitable for server-to-server communication where the caller is trusted.
Short-Lived Mode
Instead of returning the raw secret, keyzero signs a short-lived JWT that the caller can exchange for the real credential through the credential-swapping proxy.
resources:
- ref: "secret/data/myapp/api/**"
resolver: vault-prod
mode: short_lived
ttl: 300
credential_location: "header:X-API-Key:"
Globs work with short_lived mode too -- you can use patterns like secret/data/myapp/api/** to match multiple refs under the same short-lived policy.
Response:
{
"results": {
"secret/data/myapp/api/key": {
"mode": "short_lived",
"ttl": 300,
"token": "eyJ...",
"proxy": "https://proxy.example.com:8443"
}
}
}
How it works:
- Policy allows the request
- keyzero signs a short-lived JWT containing the resource
ref,iss: keyzero,aud: keyzero-proxy, and an expiration - The caller receives the JWT and proxy URL -- not the raw secret
- The caller sends requests through the proxy using the short-lived JWT as the Bearer token
- The proxy verifies the JWT, fetches the real secret, swaps the credential, and forwards the request
Requirements:
KEYZERO_SIGNING_KEY_PATH-- RSA private key for signing short-lived JWTs (on thekz server startinstance)KEYZERO_PUBLIC_KEY_PATH-- RSA public key for verification (on thekz server start --proxyinstance)KEYZERO_PROXY_URL-- the proxy's public URL, returned in responses
The Credential-Swapping Proxy
The proxy (kz server start --proxy) handles the second half of the short-lived flow.
KEYZERO_PUBLIC_KEY_PATH=./public.pem \
kz server start --bundle ./bundle.yaml --proxy --listen 0.0.0.0:8443
Proxy request flow:
- Caller sends a request to the proxy with
Authorization: Bearer <short-lived-jwt> - Proxy verifies the JWT (issuer:
keyzero, audience:keyzero-proxy, not expired) - Proxy extracts the resource
reffrom the JWT claims - Proxy resolves the real secret from the backend
- Proxy swaps the credential based on
credential_location - Proxy forwards the request to the target URL
- Proxy returns the upstream response to the caller
The caller never sees the real secret. It only appears on the wire between the proxy and the upstream service.
credential_location Format
Controls where the real credential is placed in the forwarded request.
| Format | Behavior | Example Header |
|---|---|---|
header:Authorization:Bearer | Authorization: Bearer <secret> | OAuth/JWT APIs |
header:X-API-Key: | X-API-Key: <secret> | API key authentication |
header:<name>:<prefix> | <name>: <prefix> <secret> | Custom headers |
header:<name>: | <name>: <secret> | No prefix |
Default: header:Authorization:Bearer
If the prefix is empty (trailing colon), the secret is placed as the header value without any prefix.
When to Use Which
| Scenario | Mode | Why |
|---|---|---|
| Server-to-server in a trusted network | direct | Simple; caller is already trusted |
| CI/CD pipelines (GitHub Actions, etc.) | direct | Short-lived runner, secrets used once |
| AI agents (MCP) | direct with fetch tool | The fetch tool already hides the secret |
| Untrusted clients that need API access | short_lived | Client never sees raw credentials |
| Long-running services making API calls | short_lived | Credentials rotate with each JWT expiration |
| Browser/mobile clients via backend proxy | short_lived | Keep secrets server-side only |
General rule: Use direct when the caller is trusted and the secret exits only within your infrastructure. Use short_lived when you want to grant API access without exposing the credential.