Secrets Handling¶
This page documents how Gondolin handles API keys/tokens so the guest can use secrets without directly reading them.
See also:
Quick Model¶
Gondolin allows you to not put real secret values into the VM environment.
Instead, with createHttpHooks({ secrets: ... }):
- The host generates placeholders (
GONDOLIN_SECRET_<random>by default) - You pass
env+httpHooksintoVM.create(...) - The guest only sees placeholders in env vars
- On outbound HTTP, the host replaces placeholders with real values (only for allowed hosts)
If a placeholder is used for a disallowed host, the request is blocked.
SDK Usage¶
import { VM, createHttpHooks } from "@earendil-works/gondolin";
const { httpHooks, env } = createHttpHooks({
allowedHosts: ["api.github.com"],
secrets: {
GITHUB_TOKEN: {
hosts: ["api.github.com"],
value: process.env.GITHUB_TOKEN!,
},
},
});
const vm = await VM.create({ httpHooks, env });
Important: pass both httpHooks and env. If you only pass httpHooks,
the guest will not have placeholder env vars to reference.
Custom Placeholders¶
A secret can provide a fixed placeholder string or a function that returns one.
The function is called once when createHttpHooks() is called.
Custom placeholders must be high-entropy, unique values. Gondolin matches the
exact placeholder bytes in outbound headers (and query strings when enabled), so
low-entropy placeholders can collide with normal request data or other secret
values and cause unintended substitution, request blocking, or skipped
substitution. Prefer makePlaceholderFunc() with enough random characters;
fixed placeholders should only be used for compatibility with token-shaped,
high-entropy values.
import {
BASE62_ALPHABET,
createHttpHooks,
makePlaceholderFunc,
} from "@earendil-works/gondolin";
const { httpHooks, env } = createHttpHooks({
secrets: {
GITHUB_TOKEN: {
hosts: ["api.github.com"],
value: process.env.GITHUB_TOKEN!,
placeholder: makePlaceholderFunc({
prefix: "ghp_",
length: 36,
suffix: "",
alphabet: BASE62_ALPHABET,
}),
},
},
});
Only the exact generated placeholder value is substituted; Gondolin does not replace arbitrary strings that merely look like matching tokens.
What Is Substituted¶
By default, placeholder substitution happens in request headers.
Supported by default:
- Plain header values (for example
Authorization: Bearer $TOKEN) Authorization: Basic ...andProxy-Authorization: Basic ...- Gondolin decodes base64
username:password, replaces placeholders, and re-encodes
Optional:
- URL query string (
replaceSecretsInQuery: true)
Not substituted:
- Request body
- URL path
- Response content
Host Matching and Allowlists¶
Each secret has its own host pattern allowlist (secrets.NAME.hosts). Patterns
are case-insensitive and support * wildcards.
createHttpHooks keeps the global network host allowlist separate from per-secret
substitution scopes:
allowedHostscontrols global egress policy (undefined= allow all, explicit[]= deny all)secrets.*.hostsonly controls where that specific secret may be substituted
So configuring a secret does not narrow or expand the global host allowlist on its own.
Hook Ordering¶
createHttpHooks applies secret replacement as part of its hook implementation.
Important:
- In
createHttpHooks, user-providedonRequesthandlers may run after placeholder substitution - There is no guarantee that custom hooks (
onRequest/onResponse) only see placeholders
Do not log request headers/URLs from hooks unless you are comfortable potentially logging real secret values.
CLI Equivalent¶
CLI --host-secret NAME@HOST[,HOST...][=VALUE] uses the same mechanism.
- If
=VALUEis omitted, the value is read from host env varNAME - Inside the guest,
$NAMEis a placeholder, not the real value
Operational guidance¶
- Prefer header-based auth over query parameters
- Keep
replaceSecretsInQuerydisabled unless a target API requires it - Do not pass real secrets via
VM.envor image build configenv - Do not mount host secret files (
~/.aws,.env, etc.) into the guest - Treat allowed hosts as trusted egress: guest-readable data can be uploaded there