#263 Vault expressions silently deliver __SWAMP_VSEC__ sentinels under the docker driver
Opened by john · 5/6/2026· Shipped 5/6/2026
What's broken
Vault expressions in step inputs: advertise per-step runtime resolution — swamp-vault skill: "Vault expressions are resolved per-step at execution time — each step gets a fresh vault read." This holds under the raw driver, but the docker driver delivers the raw __SWAMP_VSEC_<hash>_<n>__ sentinel string to the model instead of the decrypted value, with no error and no warning.
Concrete symptom: a step that does claudeCredentials: \${{ vault.get(my-vault, my-key) }} and writes the value to disk inside the container ends up persisting a 25-byte file containing literally __SWAMP_VSEC_b0a22fa1_0__. The model has no signal that resolution failed.
Reproduce
- Create any vault that returns a non-empty string (we hit it with
@webframp/macos-keychain, but it's not vault-type-specific — it's about how the docker driver moves args). - Author an extension model with a string arg, and have its
executewrite that arg to a file. - Set the model up with the
dockerdriver. - In a workflow step, set
inputs.<argName>: \${{ vault.get(<vault>, <key>) }}. - Run the workflow. The file on disk inside the container will contain
__SWAMP_VSEC_<hash>_<n>__, not the secret.
Removing z.meta({ sensitive: true }) from the arg's zod schema makes no difference — the sentinel is generated upstream of the schema.
Where the gap lives (from a quick code dive)
- Sentinel generator:
src/domain/vaults/vault_secret_bag.ts:70(VaultSecretBag.addSecret()), called unconditionally fromModelResolver.resolveVaultExpressions()atsrc/domain/expressions/model_resolver.ts:856. - Resolver paths used elsewhere:
resolveRaw,resolveDeep,resolveForShellatvault_secret_bag.ts:84–143. - Raw-driver method execution resolves them:
DefaultMethodExecutionServicecallssecretBag.resolveDeep(methodArgs)at line ~248 before invoking the model'sexecute. - Shell model resolves via env-var injection:
domain/models/command/shell/shell_model.ts:109callsresolveForShell(). - Docker driver:
src/domain/drivers/docker_execution_driver.ts:529–532acceptsdriverConfig.envand method args, but does not callresolveDeep()orresolveForShell()on either.methodArgsare serialised into the bundle request payload with sentinels intact;envvalues are passed via-e KEY=VALUEliterally.
Why this is a bug, not a docs gap
Two reasons:
- The contract is advertised as driver-agnostic. The
swamp-vaultskill describes per-step resolution as a property of vault expressions, with no caveat about the docker driver. The principle of least surprise says either it works or it errors loudly. - The current behaviour is silent data corruption from the model author's perspective. The model receives a string that's syntactically valid for its schema but semantically wrong. There's no distinguishable failure mode — the model just operates on garbage and produces nonsense outputs. We hit this and only diagnosed it by hand-inspecting an on-disk artefact.
Suggested fix
The docker driver should resolve sentinels before invoking docker, in one of two ways:
- Inline-substitute into method args (mirroring
resolveDeep's effect for raw): the JSON payload sent into the container already contains plaintext. Simple, but exposes secrets in the bundle request file on disk. - Forward as env vars (mirroring
resolveForShell's pattern): pass-e __SWAMP_VAULT_N=valuefor each sentinel, and ship a small replacement table to the model's bundle entrypoint that swaps__SWAMP_VSEC_<...>__placeholders in method args back to\$__SWAMP_VAULT_Nenv reads at the moment of execute. Keeps the secret out of the request file on disk.
Either is a clear improvement over today's silent passthrough.
Workaround (currently)
Add a raw-driver pre-step that does the vault read and writes the secret to a host path that the docker step mounts. Works, but: (a) requires every workflow author to know about this gap; (b) writes the secret to the host filesystem unconditionally; (c) breaks the declarative "just put the vault expression where you need the value" model swamp otherwise sells very well.
Environment
- swamp
20260206.200442.0-sha.(CLI) /20260505.231643.0-sha.5a337b81(vault subcommands) - macOS host, Debian-based docker image (
denoland/deno:debian-2.4.0) - Vault provider:
@webframp/macos-keychain2026.04.22.3 (but the issue is in the driver, not the vault)
Shipped
Click a lifecycle step above to view its details.
Sign in to post a ripple.