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:

  1. Policy allows the request
  2. keyzero fetches the secret from the backend
  3. 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:

  1. Policy allows the request
  2. keyzero signs a short-lived JWT containing the resource ref, iss: keyzero, aud: keyzero-proxy, and an expiration
  3. The caller receives the JWT and proxy URL -- not the raw secret
  4. The caller sends requests through the proxy using the short-lived JWT as the Bearer token
  5. 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 the kz server start instance)
  • KEYZERO_PUBLIC_KEY_PATH -- RSA public key for verification (on the kz server start --proxy instance)
  • 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:

  1. Caller sends a request to the proxy with Authorization: Bearer <short-lived-jwt>
  2. Proxy verifies the JWT (issuer: keyzero, audience: keyzero-proxy, not expired)
  3. Proxy extracts the resource ref from the JWT claims
  4. Proxy resolves the real secret from the backend
  5. Proxy swaps the credential based on credential_location
  6. Proxy forwards the request to the target URL
  7. 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.

FormatBehaviorExample Header
header:Authorization:BearerAuthorization: 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

ScenarioModeWhy
Server-to-server in a trusted networkdirectSimple; caller is already trusted
CI/CD pipelines (GitHub Actions, etc.)directShort-lived runner, secrets used once
AI agents (MCP)direct with fetch toolThe fetch tool already hides the secret
Untrusted clients that need API accessshort_livedClient never sees raw credentials
Long-running services making API callsshort_livedCredentials rotate with each JWT expiration
Browser/mobile clients via backend proxyshort_livedKeep 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.