Host command credentials (process source)
Run a host command and use its trimmed stdout as the credential value. The process source is the escape hatch for secret managers gatekeeper has no dedicated integration for: any CLI that prints a credential — a password manager, an OS keychain tool, a corporate credential helper — can back a grant without writing Go code.
When to use it
Use process when the credential lives behind a command-line tool rather than an API gatekeeper speaks natively (AWS Secrets Manager, GCP Secret Manager, GitHub Apps). This covers bring-your-own secret managers: 1Password, pass, a Vault CLI wrapper, or an internal credential_process-style helper.
If the backend has a stable HTTP API, prefer a dedicated source (AWS Secrets Manager, GCP Secret Manager) — those fetch over HTTPS directly instead of shelling out.
Prerequisites
- CA certificate generated (CA Setup)
- A command, runnable by the user gatekeeper runs as, that prints a credential to stdout and exits 0
Configuration
Add a process credential source to gatekeeper.yaml:
proxy:
host: 127.0.0.1
port: 9080
tls:
ca_cert: ca.crt
ca_key: ca.key
credentials:
- host: api.example.com
header: x-api-key
grant: example-api
source:
type: process
command: "op read op://vault/example/api-key"
ttl: 10m
network:
policy: permissive
log:
level: info
format: text
| Field | Required | Default | Description |
|---|---|---|---|
command | Yes | — | Shell command, run with sh -c. Trimmed stdout becomes the credential |
ttl | No | 5m | Refresh interval when the output carries no expiry (Go duration string) |
Worked example: 1Password CLI
Store the credential in a 1Password vault, then reference it by its op:// path:
credentials:
- host: api.github.com
grant: github
source:
type: process
command: "op read op://Engineering/github-pat/credential"
op must be signed in (op signin) in whatever session gatekeeper’s process inherits, or configured with a service account token via its own environment variable — gatekeeper only runs the command, it does not manage op’s auth state.
Worked example: pass
pass prints the decrypted secret to stdout:
credentials:
- host: api.example.com
header: x-api-key
grant: example-api
source:
type: process
command: "pass show api/example-api-key"
pass shells out to GPG, so gatekeeper’s process needs access to the same gpg-agent (and cached passphrase, or an unattended key) that an interactive pass show would need. A command that blocks on a passphrase prompt blocks the credential fetch — and, on a refresh, the background refresh goroutine — until it times out.
Refresh semantics
process implements background refresh the same way GitHub App tokens do: gatekeeper re-runs the command at 75% of the credential’s TTL (floored at 30 seconds) and hot-swaps the value without downtime.
How the TTL is determined depends on the command’s output:
- Expiry-aware. If stdout is JSON matching the AWS
credential_processshape — exact-caseVersion,AccessKeyId, andExpiration(RFC 3339) keys — gatekeeper readsExpirationand schedules the refresh from it. The configuredttlis ignored in this case. The JSON is passed through verbatim as the credential value; gatekeeper does not extractAccessKeyIdor any other field for you. - Plain string or other JSON. Anything else — a bare token, an API key, JSON without that exact key set — is treated as an opaque value with no expiry, and the configured
ttl(default 5 minutes) sets the refresh interval.
# Expiry-aware: gatekeeper schedules refresh from "Expiration", ignores ttl.
{"Version":1,"AccessKeyId":"...","SecretAccessKey":"...","Expiration":"2026-07-13T18:00:00Z"}
# Opaque: gatekeeper refreshes every ttl (or every 5m by default).
ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Failure behavior
At startup, a failing command fails gatekeeper’s startup: the credential is fetched once before the proxy begins serving, and a non-zero exit or empty output is a fatal configuration error.
On refresh, a failing command does not take gatekeeper down or drop the previously-injected credential — the last good value stays in place while the refresh loop retries with exponential backoff (starting at 1 second, capped at 60 seconds, with jitter).
A few specific failure modes:
- Non-zero exit. The fetch fails; stderr (truncated to 256 bytes) is included in the error so a diagnosable failure — an expired SSO session, a locked keychain — shows up in logs. Stdout is never included, since it may hold a partial credential.
- Empty output. Treated as an error even on exit 0.
- Already-expired
Expiration. If the sniffedcredential_processJSON reports anExpirationalready in the past, the fetch fails instead of installing a credential that would be rejected upstream on first use. - Control characters in output. Bytes invalid in HTTP header values (RFC 7230) are stripped automatically. If any non-whitespace control byte was present, gatekeeper logs a warning with a count — never the value — so a helper emitting garbage is diagnosable without exposing the credential.
Security notes
Warning: The command runs on the host with gatekeeper’s own OS privileges, not the client’s. Only configure commands from config files you trust — an embedder that lets an untrusted party supply
gatekeeper.yaml(or just thecommandfield) is granting that party arbitrary code execution as the gatekeeper process.
- Don’t echo secrets to logs. Gatekeeper never logs the fetched value, but a command that itself writes the credential to stderr (for debugging) will have that stderr echoed into gatekeeper’s error log on failure — keep debug output out of commands used as credential sources.
- Prefer a command that reads from an already-unlocked store over one that prompts interactively. A prompt blocks the calling goroutine, and on a background refresh, there’s no terminal attached to answer it.
Next steps
- GitHub App Tokens — a source with the same 75%-of-TTL refresh model, built in
- Network Lockdown — restrict proxy traffic to specific hosts