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

yaml
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:

sh
OPENWEATHER_KEY=... node packages/cli/bin/kervan.js run weather.yaml
node packages/cli/bin/kervan.js run weather.yaml --env-file .env

In 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:

yaml
secrets:
  - OTHER_KEY                    # unrestricted
  - name: API_KEY
    hosts:
      - api.example.com          # port 443
      - api.example.com:8443     # this port only
  • Entries are host or host:port; without a port, the binding means port 443. A secret bound to api.example.com is never sent to api.example.com:8443.
  • Hosts and ports match exactly, after normalization (case, IDNA, IP forms). example.com does not cover api.example.com, and wildcards, schemes and paths are refused.
  • Write host:port without a space after the colon. YAML reads api.example.com: 8443 as 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, Accept and User-Agent are 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.