You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is AI slop, triggered by @mitsuhiko. It's a brainstorm written up as a proposal — a point to discuss, not yet to implement.
Context
#121 adds runtime add/update/delete for HTTP-hook secrets, so secrets can change after VM startup and newly-spawned processes pick up the current placeholders. This proposal questions one assumption underneath that PR — the per-secret, dynamically-generated, high-entropy placeholder — and sketches an alternative that would largely dissolve the runtime-propagation problem #121 is solving.
How placeholders work today
A placeholder does two jobs at once:
It's the env var's value — API_KEY=GONDOLIN_SECRET_<48 hex> — so tools that read os.environ["API_KEY"] transparently pick it up.
It's the detection token — replaceSecretPlaceholdersInString exact-substring-matches each placeholder against outbound headers/query and swaps in the real value only when the destination host is allowed.
The entropy is not a confidentiality property — the guest already sees every placeholder in its env. It exists purely for collision avoidance: so real request content never accidentally matches a placeholder, and so placeholders don't overlap each other (hence assertSecretPlaceholderIsSafe).
Because placeholders are dynamic and per-secret, every newly-added secret introduces a new opaque token that must be propagated to every newly-spawned process. That propagation is most of #121's surface area: rewriting /etc/gondolin/secrets.env, the mergeExecEnvWithRuntimeSecrets filter-and-merge, deleted-name stripping, and the reuse-the-old-placeholder-on-re-add logic.
The idea
We don't actually need high entropy per secret. We only need a high-entropy marker to reliably locate an injection point. So: use one static high-entropy marker for "secret injection here," and append a well-known per-secret identifier.
$GONDOLIN_SECRET.github → <MARKER>.github
The host scans outbound content for <MARKER>.<identifier>, looks the identifier up in a host-side map, and substitutes the real value (subject to the existing per-secret host allowlist). Detection is now structured parsing of a static marker, not exact matching against a dynamically-known set. That decouples detection from enrollment timing.
Why this helps
Only one env var (GONDOLIN_SECRET) is ever in the guest env, and it never changes — no env churn on add/update/delete.
Add/delete becomes a pure host-side map mutation; newly-spawned processes need nothing refreshed.
Re-adding a deleted secret reuses its "placeholder" for free (it's deterministic from the identifier).
Per-secret overlap checks collapse into a single grammar.
Dynamic lookups: a secret can be referenced before it's added; resolution simply happens (or not) host-side once the secret exists.
The acknowledged downside
You lose customizable placeholders. Some clients validate token shape (e.g. ghp_…), and the marker scheme can't mimic that. So this should be an additional mode, not a removal of the current placeholder?: string | (() => string) option.
The fork that decides everything: the env-var question
Model A — explicit reference only. Guest references $GONDOLIN_SECRET.github by convention. One env var total. Simplest, best dynamic-lookup story. But tools expecting a natural OPENAI_API_KEY won't find one — transparent pickup is lost.
Model B — keep per-secret env vars, derived values.OPENAI_API_KEY=<MARKER>.openai_api_key. Transparent pickup preserved; values deterministic, so they can be populated the instant a secret is named (before its value exists host-side), and add/remove toggles resolution without rewriting guest env.
Recommendation: B as default, A always available. That delivers dynamic lookups and keeps #121's propagation near-zero.
Open design questions
Marker scope. Recommend static within a session but randomly generated per createHttpHooks — keeps collision-avoidance, avoids a marker shared across tenants/VMs, still stable across processes and restarts. "Static" = "not per-secret," not necessarily "compiled-in constant."
Suffix grammar / maximal munch. Pick an identifier charset (e.g. [A-Za-z0-9_-]) with . as separator. Maximal-munch is unambiguous as long as identifiers are bounded by non-identifier chars (<MARKER>.github/repos → github). Footgun: <MARKER>.githubX → id githubX, not github+X. Document it.
Unknown / deleted identifier policy.<MARKER>.doesnotexist → leave literal, blank, or block? Lean toward blank (consistent with deleted-secret behavior, avoids leaking the marker downstream).
Coexistence with custom placeholders. Keep the existing custom-placeholder mode for shape-sensitive tokens; the marker scheme is opt-in/default-able alongside it.
Security: discoverability. With random per-secret placeholders, a process without a secret in its env effectively can't use it (can't guess the token). With well-known identifiers, any process can reference any secret by name, toward that secret's allowed hosts. The host allowlist was always the real boundary, so this is not a new exfiltration path — but it widens "secrets a given process can exercise" from "those in its env" to "all of them." Worth stating as a considered trade-off.
Orthogonal machinery that stays. Value revocation (revokedValues) and the defense-in-depth "real value to disallowed host" checks are independent of the placeholder scheme and carry over unchanged.
Ask
Discuss the approach (especially the A-vs-B env-var question and marker scope) before anything is built. If we like it, this likely simplifies — rather than extends — the direction in #121.
Note
This is AI slop, triggered by @mitsuhiko. It's a brainstorm written up as a proposal — a point to discuss, not yet to implement.
Context
#121 adds runtime add/update/delete for HTTP-hook secrets, so secrets can change after VM startup and newly-spawned processes pick up the current placeholders. This proposal questions one assumption underneath that PR — the per-secret, dynamically-generated, high-entropy placeholder — and sketches an alternative that would largely dissolve the runtime-propagation problem #121 is solving.
How placeholders work today
A placeholder does two jobs at once:
API_KEY=GONDOLIN_SECRET_<48 hex>— so tools that reados.environ["API_KEY"]transparently pick it up.replaceSecretPlaceholdersInStringexact-substring-matches each placeholder against outbound headers/query and swaps in the real value only when the destination host is allowed.The entropy is not a confidentiality property — the guest already sees every placeholder in its env. It exists purely for collision avoidance: so real request content never accidentally matches a placeholder, and so placeholders don't overlap each other (hence
assertSecretPlaceholderIsSafe).Because placeholders are dynamic and per-secret, every newly-added secret introduces a new opaque token that must be propagated to every newly-spawned process. That propagation is most of #121's surface area: rewriting
/etc/gondolin/secrets.env, themergeExecEnvWithRuntimeSecretsfilter-and-merge, deleted-name stripping, and the reuse-the-old-placeholder-on-re-add logic.The idea
We don't actually need high entropy per secret. We only need a high-entropy marker to reliably locate an injection point. So: use one static high-entropy marker for "secret injection here," and append a well-known per-secret identifier.
The host scans outbound content for
<MARKER>.<identifier>, looks the identifier up in a host-side map, and substitutes the real value (subject to the existing per-secret host allowlist). Detection is now structured parsing of a static marker, not exact matching against a dynamically-known set. That decouples detection from enrollment timing.Why this helps
GONDOLIN_SECRET) is ever in the guest env, and it never changes — no env churn on add/update/delete.The acknowledged downside
You lose customizable placeholders. Some clients validate token shape (e.g.
ghp_…), and the marker scheme can't mimic that. So this should be an additional mode, not a removal of the currentplaceholder?: string | (() => string)option.The fork that decides everything: the env-var question
$GONDOLIN_SECRET.githubby convention. One env var total. Simplest, best dynamic-lookup story. But tools expecting a naturalOPENAI_API_KEYwon't find one — transparent pickup is lost.OPENAI_API_KEY=<MARKER>.openai_api_key. Transparent pickup preserved; values deterministic, so they can be populated the instant a secret is named (before its value exists host-side), and add/remove toggles resolution without rewriting guest env.Recommendation: B as default, A always available. That delivers dynamic lookups and keeps #121's propagation near-zero.
Open design questions
createHttpHooks— keeps collision-avoidance, avoids a marker shared across tenants/VMs, still stable across processes and restarts. "Static" = "not per-secret," not necessarily "compiled-in constant."[A-Za-z0-9_-]) with.as separator. Maximal-munch is unambiguous as long as identifiers are bounded by non-identifier chars (<MARKER>.github/repos→github). Footgun:<MARKER>.githubX→ idgithubX, notgithub+X. Document it.<MARKER>.doesnotexist→ leave literal, blank, or block? Lean toward blank (consistent with deleted-secret behavior, avoids leaking the marker downstream).revokedValues) and the defense-in-depth "real value to disallowed host" checks are independent of the placeholder scheme and carry over unchanged.Ask
Discuss the approach (especially the A-vs-B env-var question and marker scope) before anything is built. If we like it, this likely simplifies — rather than extends — the direction in #121.