Credential sources
Each credential entry in gatekeeper.yaml includes a source block that determines where the credential value comes from.
Source types overview
| Type | Description | Refresh |
|---|---|---|
env | Read from an environment variable | No |
static | Literal inline value | No |
process | Run a host command, use its stdout | Yes (auto-refresh; expiry read from credential_process-format JSON, else configurable ttl) |
aws-secretsmanager | Fetch from AWS Secrets Manager | No |
gcp-secretmanager | Fetch from GCP Secret Manager | No |
gcp-service-account | Mint GCP OAuth2 access token from a service account key | Yes (auto-refresh before expiry) |
github-app | Generate GitHub App installation token | Yes (auto-refresh before expiry) |
token-exchange | RFC 8693 token exchange | Yes (per-request, cached with TTL) |
Sources marked Refresh: Yes have credentials that expire. process, github-app, and gcp-service-account implement background credential refresh — gatekeeper re-fetches at 75% of TTL (minimum 30 seconds) and hot-swaps without downtime. token-exchange uses per-request lazy caching: on cache miss, gatekeeper calls the STS and caches the result for the token’s TTL.
env
Read the credential value from an environment variable at startup.
credentials:
- host: api.github.com
source:
type: env
var: GITHUB_TOKEN
var
Name of the environment variable to read.
- Type:
string - Required: Yes
- Default: —
The variable must be set and non-empty at startup. If unset, gatekeeper exits with an error.
static
Use a literal value defined inline in the config file.
credentials:
- host: api.example.com
header: x-api-key
source:
type: static
value: sk-xxxx
value
The credential value.
- Type:
string - Required: Yes
- Default: —
Note: Avoid committing secrets in config files. Prefer
envor a secret manager source for production deployments.
process
Run a host command and use its stdout as the credential value. Any helper
that prints a credential works: OS keychain CLIs, pass, 1Password’s op,
or an AWS credential_process helper.
credentials:
- host: api.example.com
header: x-api-key
source:
type: process
command: "op read op://vault/example/api-key"
command
Shell command to run with sh -c. Stdout (trimmed) becomes the credential
value.
- Type:
string - Required: Yes
- Default: —
ttl
Refresh interval when the command output carries no expiry information, as
a Go duration (90s, 30m, 12h). Must be positive.
- Type:
string - Required: No
- Default:
5m
Like all refreshing sources, gatekeeper re-fetches at 75% of TTL (floor 30
seconds), so the default re-runs the command every 3m45s. Set a longer
ttl for helpers that are expensive or interactive (biometric prompts,
rate limits) and serve values that rarely change.
Behavior
- Expiry-aware refresh. When stdout is AWS
credential_process-format JSON — recognized by its exact-caseVersion,AccessKeyId, andExpiration(RFC 3339) keys — the credential refreshes on that expiry, andttlis ignored. The JSON is passed through as the credential value; consumers that need the individual fields parse it themselves. Other JSON output is treated as an opaque credential and refreshes onttl. - Expired output is an error. If the reported
Expirationis already in the past, the fetch fails (with the timestamp in the error) instead of installing credentials that would be rejected upstream; the standard retry backoff applies. - Sanitization. Control characters that are invalid in HTTP header values (RFC 7230) are stripped. A warning is logged (count only — the value is never logged) when non-whitespace control bytes were present; trailing newlines and pretty-printed JSON are normal and do not warn.
- Failures. A non-zero exit fails the fetch; stderr is included in the error (truncated) so helper failures such as an expired SSO session are diagnosable. Empty output is an error. Failed refreshes retry with the standard backoff.
Security: The command runs on the host with gatekeeper’s privileges. Only configure commands you trust, from config files you own. Embedding operators (like moat) must not accept this field from repository-controlled config.
aws-secretsmanager
Fetch the credential from AWS Secrets Manager at startup. Uses the AWS SDK default credential chain (AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY, IAM roles, etc.).
credentials:
- host: api.example.com
source:
type: aws-secretsmanager
secret: my-app/api-key
region: us-east-1
secret
AWS Secrets Manager secret ID or ARN.
- Type:
string - Required: Yes
- Default: —
region
AWS region for the Secrets Manager client.
- Type:
string - Required: No
- Default: — (uses
AWS_REGIONorAWS_DEFAULT_REGIONfrom the environment)
gcp-secretmanager
Fetch the credential from GCP Secret Manager at startup. Uses Application Default Credentials (GOOGLE_APPLICATION_CREDENTIALS, metadata server, etc.).
credentials:
- host: api.example.com
source:
type: gcp-secretmanager
project: my-gcp-project
secret: api-key
version: latest
project
GCP project ID containing the secret.
- Type:
string - Required: Yes
- Default: —
secret
Secret name within the project.
- Type:
string - Required: Yes
- Default: —
version
Secret version to access.
- Type:
string - Required: No
- Default:
"latest"
The underlying gRPC connection is closed on gatekeeper shutdown.
gcp-service-account
Mint short-lived GCP OAuth2 access tokens from a service account key (the JSON file format produced by gcloud iam service-accounts keys create). Gatekeeper signs a JWT with the key, exchanges it for an access token at Google’s OAuth2 token endpoint, and refreshes the token automatically in the background before expiry. Clients inside the sandbox never see the service account key — only the proxy holds it.
credentials:
- host: storage.googleapis.com
grant: gcs
source:
type: gcp-service-account
project: my-gcp-project
secret: gcs-uploader-key
scopes: https://www.googleapis.com/auth/devstorage.read_write
The service account key JSON can come from one of three locations: GCP Secret Manager (secret + project), a file (private_key_path), or an environment variable (private_key_env). Exactly one must be set.
secret
GCP Secret Manager secret holding the service account key JSON. Reading the secret uses Application Default Credentials, same as the gcp-secretmanager source. If the token endpoint rejects an assertion (e.g., after the key is rotated and the old key revoked), gatekeeper drops the cached key and re-reads it from Secret Manager on the next refresh, so key rotation is picked up without a restart.
- Type:
string - Required: One of
secret,private_key_path, orprivate_key_envis required - Default: —
project
GCP project ID containing the secret.
- Type:
string - Required: Yes, when
secretis set - Default: —
version
Secret version to access.
- Type:
string - Required: No (only valid with
secret) - Default:
"latest"
private_key_path
File path to the service account key JSON.
- Type:
string - Required: One of
secret,private_key_path, orprivate_key_envis required - Default: —
private_key_env
Name of an environment variable containing the service account key JSON.
- Type:
string - Required: One of
secret,private_key_path, orprivate_key_envis required - Default: —
The environment variable must be set and non-empty at startup.
scopes
Space-separated list of OAuth scopes to request.
- Type:
string - Required: No
- Default:
"https://www.googleapis.com/auth/cloud-platform"
github-app
Generate short-lived GitHub App installation access tokens. Tokens refresh automatically in the background before expiry.
credentials:
- host: api.github.com
grant: github
source:
type: github-app
app_id: "12345"
installation_id: "67890"
private_key_path: /etc/gatekeeper/github-app.pem
app_id
GitHub App ID.
- Type:
string - Required: Yes
- Default: —
installation_id
GitHub App installation ID.
- Type:
string - Required: Yes
- Default: —
private_key_path
File path to the PEM-encoded RSA private key for the GitHub App.
- Type:
string - Required: One of
private_key_pathorprivate_key_envis required - Default: —
Mutually exclusive with private_key_env. Supports both PKCS#1 (RSA PRIVATE KEY) and PKCS#8 (PRIVATE KEY) PEM formats.
private_key_env
Name of an environment variable containing the PEM-encoded RSA private key.
- Type:
string - Required: One of
private_key_pathorprivate_key_envis required - Default: —
Mutually exclusive with private_key_path. The environment variable must be set and non-empty at startup.
When multiple credentials share the same github-app source config (e.g., api.github.com and github.com), gatekeeper deduplicates them into a single token fetch and a single background refresh goroutine.
token-exchange
Exchange a per-request subject token for an access token via RFC 8693 (OAuth 2.0 Token Exchange). Unlike other sources, token-exchange resolves credentials dynamically per request rather than at startup.
credentials:
- host: api.github.com
grant: github
source:
type: token-exchange
endpoint: https://sts.example.com/token
client_id: gatekeeper
client_secret_env: STS_CLIENT_SECRET
subject_header: X-Subject-Token
resource: https://api.github.com
endpoint
STS token endpoint URL.
- Type:
string - Required: Yes
- Default: —
client_id
OAuth client ID for authenticating to the STS via HTTP Basic auth.
- Type:
string - Required: Yes
- Default: —
client_secret
OAuth client secret. Sent as the Basic auth password to the STS endpoint.
- Type:
string - Required: One of
client_secretorclient_secret_envis required - Default: —
Mutually exclusive with client_secret_env.
client_secret_env
Name of an environment variable containing the OAuth client secret.
- Type:
string - Required: One of
client_secretorclient_secret_envis required - Default: —
Mutually exclusive with client_secret. The environment variable must be set and non-empty at startup.
subject_header
HTTP request header containing the subject token. The header is stripped from the request before forwarding.
- Type:
string - Required: One of
subject_headerorsubject_fromis required - Default: —
Mutually exclusive with subject_from.
subject_from
Alternative subject token extraction method.
- Type:
string - Required: One of
subject_headerorsubject_fromis required - Default: —
- Valid values:
"proxy-auth"
When set to "proxy-auth", the subject token is extracted from the username in the Proxy-Authorization Basic auth header.
Mutually exclusive with subject_header.
subject_token_type
OAuth token type URI for the subject token.
- Type:
string - Required: No
- Default:
"urn:ietf:params:oauth:token-type:access_token"
resource
Target resource URI included in the token exchange request.
- Type:
string - Required: No
- Default: —
actor_token_from
Source for the optional RFC 8693 actor token.
- Type:
string - Required: No
- Default: — (no actor token)
- Valid values:
"proxy-auth-password"
When set to "proxy-auth-password", the actor token is extracted from the password in the Proxy-Authorization Basic auth header. Requires subject_from: proxy-auth.
When actor_token_from is configured, gatekeeper requires clients to provide a non-empty proxy auth password, and requires proxy.auth_token or proxy.auth_token_env: a config without one fails at load. A password that does not equal the shared token is rejected with 407 before it reaches the STS; see proxy.auth_token.
actor_token_type
OAuth token type URI for the actor token.
- Type:
string - Required: No
- Default:
"urn:ietf:params:oauth:token-type:access_token"
bot_subject
A sentinel subject value that skips the STS exchange and falls through to the next matching credential rule for the host — the same (nil, nil) fallthrough an empty subject already gets. See Token Exchange: Bot/service fallback for the full pattern and rationale.
- Type:
string - Required: No
- Default:
""(disabled — no subject is special-cased)
Requires subject_from: proxy-auth — enforced, not just documented: setting bot_subject alongside subject_header is a config-load error. subject_header mode reads a caller-controlled, self-asserted header with nothing authenticating it, so allowing bot_subject there would let any caller send the sentinel value in that header to skip the STS and fall through to the credential below, bypassing per-subject authentication for the host. Pick a value that cannot collide with a real subject your deployment could see — "-" is a safe default.
static_fallback
What a request gets when the exchange yields no token: an empty or bot_subject subject, or an STS decline. auto injects the host’s static credentials, as before. explicit injects only the static credentials whose header the client sent, and otherwise refuses the request with 403 and X-Gatekeeper-Blocked: credential-required. Use explicit on a per-user subscription rule, so that a request with no subscription token never falls back silently to a static API key on the same host.
With explicit, a 404 from the STS also counts as “no token for this subject”. The 403 body then ends with the STS’s error_description, when it is printable ASCII, cut at 200 bytes, so the STS can tell the user what to do (for example, connect an account). A header that the resolver consumed, such as a subject_header, does not count as naming a static credential. Any other STS error still fails the request with 502.
- Type:
string - Required: No
- Default:
"auto"
credentials:
- host: api.anthropic.com
grant: claude-code-user
source:
type: token-exchange
subject_from: proxy-auth
static_fallback: explicit
# ...
- host: api.anthropic.com
grant: anthropic
header: x-api-key
prefix: ""
source: {type: env, var: ANTHROPIC_API_KEY}
With this config, a request that sends x-api-key gets the static key. An OAuth request with no subscription token is refused instead of billing the API key.
Exchanged tokens are cached per subject (and actor, if present) using the TTL from the STS expires_in response field, but the cached TTL is always capped at 1 minute regardless of what the STS advertises — a longer expires_in only means the token may remain valid that long, not that it stays valid, since the upstream credential behind the exchange can be revoked or rotated without gatekeeper’s knowledge. If the STS does not return expires_in (or returns a non-positive value), the same 1-minute cap is used as the TTL. Concurrent requests for the same subject are coalesced into a single STS call via singleflight, so the 1-minute cap does not translate into an STS call per request.
See Credential invalidation below for how gatekeeper evicts a cached token before its TTL expires.
Credential invalidation
When the upstream server returns 401 Unauthorized, 403 Forbidden, or 429 Too Many Requests, gatekeeper invalidates every credential that was injected into that request, so the next request re-resolves instead of replaying the cached value. This applies to any credential source that supports invalidation, not just token-exchange. A 429 can indicate that the selected backing credential exhausted its allowance while its token remains valid. Other statuses are left alone — a 5xx says nothing about the credential’s validity, and these three statuses can also cover cases where resolving again returns the same value.
For token-exchange, invalidation evicts the cached token for that subject (and actor) so the next request performs a fresh exchange. Evictions are rate-limited per subject/actor key to a 10-second cooldown: repeated 401/403/429 responses for the same key within 10 seconds are no-ops after the first eviction. This bounds how often a client looping on a failing request can force STS calls; the trade-off is that a genuinely changed backing credential can take up to 10 seconds to be picked up after the first eviction.
Invalidation is evict-only — the request that triggered it is not retried by gatekeeper.