Skip to main content
← Back to list
01Issue
FeatureOpenSwamp CLIPublic
AssigneesNone

Relationships

#1786 vault.get() arguments are verbatim, so a per-target secret cannot be expressed in workflow YAML

Opened by randybias · 8/22/2026

${{ vault.get(<vault>, <key>) }} arguments are extracted by regex and used verbatim, so neither the vault name nor the key can be supplied at run time. This makes a per-target secret inexpressible in workflow YAML.

Measured in the shipped binary (swamp --version 20260822.011157.0-sha.06b61472), strings $(command -v swamp):

/vault\.get\(\s*(?:(['"`])(.+?)\1|([^\s,)]+))\s*,\s*(?:(['"`])(.+?)\4|([^\s,)]+))\s*\)/g

Each argument is captured as a quoted string or a bare token [^\s,)]+, and the captured TEXT is passed to the vault service. So:

password: ${{ vault.get(my-vault, inputs.passwordKey) }}

looks up a key literally named inputs.passwordKey.

Why this matters beyond ergonomics. A workflow that needs a DIFFERENT secret per target has only two expressible options:

  1. a ternary over literal keys — which works, but the regex pass is branch-blind: every key named in the ternary is resolved on every run, not just the selected branch, so one missing key breaks the workflow for every target; and
  2. accept the secret as a run-time --input — which cannot carry a vault reference either (same verbatim rule), so the only thing a caller can actually pass is a plaintext secret on argv, visible in shell history, the process table and the run record.

Option 2 is what workflow authors reach for, because it is the one the input schema appears to invite. We found 15 workflows in our corpus whose input descriptions instructed callers to pass ${{ vault.get(...) }} as an --input value — several asserting outright that "swamp evaluates CEL in --input". None of them could work, and the only way to run them was a plaintext credential on the command line.

Compounding factor: every local check passes on the broken form. swamp workflow validate passes (it only checks input references). swamp workflow evaluate passes and renders the vault.get literally — because evaluate defers vault resolution by design, so a correct deferred reference and a permanently-broken one render identically. Nothing fails until a real run.

Request: CEL-evaluate vault.get() arguments, so vault.get(inputs.vaultName, inputs.secretKey) resolves the expressions before lookup. Failing that, a validate-time error when an argument is not a literal would remove the silent-failure mode entirely.

Workaround we use today: resolve the secret inside a T0 extension method instead, where context.vaultService.get(args.vaultName, args.secretKey) takes ordinary runtime strings. That works well, but it means any workflow needing a per-target secret must ship an extension method, and a purely declarative workflow cannot express it.

02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED

Open

8/22/2026, 7:27:06 PM

No activity in this phase yet.

03Sludge Pulse

Sign in to post a ripple.