Secrets and environment variables
A spec names the secrets it needs; the values come from the environment and never appear in what the server returns or logs.
Where this fits: Build with YAML. The last page of the group; then run it.
On this page
Declaring and using a secret
specVersion: 1
name: weather
version: 0.1.0
secrets: [OPENWEATHER_KEY]
tools:
- name: get_current_weather
description: Current temperature and wind for a coordinate.
annotations: { readOnlyHint: true }
input:
type: object
properties: { lat: { type: number }, lon: { type: number } }
required: [lat, lon]
http:
url: https://api.openweathermap.org/data/2.5/weather
query: { lat: "{{input.lat}}", lon: "{{input.lon}}", appid: "{{secrets.OPENWEATHER_KEY}}" }
output:
select: "{temperature: main.temp, wind: wind.speed}"Only declared names resolve. With kervan run, values come from environment variables of the same
name, or from env files:
OPENWEATHER_KEY=... node packages/cli/bin/kervan.js run weather.yaml
node packages/cli/bin/kervan.js run weather.yaml --env-file .envIn PowerShell, set the variable first: $env:OPENWEATHER_KEY = "...". Variables already set win
over the file. Node.js itself checks --env-file paths and exits with <file>: not found (code 9)
when the file is missing.
Redaction
{{secrets.NAME}} values come from a SecretSource (environment variables by default). They are
never put in error messages or logs, and every result and error a spec tool returns is scrubbed of
them, including their URL-encoded, form-encoded and JSON-escaped forms, in case the API echoes them
back. The upstream response is scrubbed before select runs on it, so an expression cannot
reshape or probe a reflected secret. Secrets shorter than 8 characters are rejected, because short
values cannot be redacted reliably.
Secrets never travel over plain http. A tool that uses a secret must call an https:// URL,
even when allowInsecureHttp is set; this is checked when the spec loads and again before every
request. For local development against an http API, kervan run --allow-insecure-secrets
(refused with NODE_ENV=production) or loadSpec(text, { allowSecretsOverHttp: true }) turns
the check off.
Binding a secret to hosts
A secret can be restricted to the hosts, and ports, it may be sent to:
secrets:
- OTHER_KEY # unrestricted
- name: API_KEY
hosts:
- api.example.com # port 443
- api.example.com:8443 # this port only- Entries are
hostorhost:port; without a port, the binding means port 443. A secret bound toapi.example.comis never sent toapi.example.com:8443. - Hosts and ports match exactly, after normalization (case, IDNA, IP forms).
example.comdoes not coverapi.example.com, and wildcards, schemes and paths are refused. - Write
host:portwithout a space after the colon. YAML readsapi.example.com: 8443as a key and value, and the load error says so. - A tool that uses a bound secret against another host is a load error.
- The executor checks again before every request and every redirect hop:
- A redirect to a host that may not receive the tool’s secrets is not followed, even when the secret would only travel inside the redirect URL (an open redirect).
- When following a redirect to another origin,
AcceptandUser-Agentare dropped too if they hold a secret.
A SecretSource can enforce its own bindings: get(name, { host, tool }) receives the
host:port a value is about to be sent to (always with the port, e.g. api.example.com:443;
normalizeHostPort normalizes an entry the same way), and returns undefined to refuse. A
spec’s hosts can only narrow what the source allows. A call then fails with “Secret X is not
configured for host:port”, whether the value is missing or not allowed there.
loadSpec(text, { requireSecrets: true }) turns the same message at load time from a warning
into an error. Use it to validate a spec before publishing it.
In code
loadSpec(text, { secrets }) takes any SecretSource, an object with
get(name, { host, tool }). envSecrets() is the default. See the
programmatic API.