Skip to main content
← Back to list
01Issue
BugClosedSwamp CLIPublic
Assigneesstack72

Relationships

#3247 Windows: model run with vault secret containing $ exits 1 silently

Opened by stack72 · 10/11/2026

A command/shell model that resolves a vault secret containing $ fails with exit code 1 and empty stderr on Windows. The vault-roundtrip fixture writes the resolved secret via WriteAllText. Manual testing confirms WriteAllText handles $100 correctly when the value is in an env var. The failure is in the model execution pipeline. resolveForPowerShell replaces sentinels with $env:__SWAMP_VAULT_0 references which should be safe. Vault put stores the value correctly. The model run itself fails silently. 1 UAT failure in e2e/security_audit_test.ts. Discovered via Windows UAT runner swamp-uat#363 on binary 20261011.150301.0-sha.323c056a.

02Bog Flow
✓OPEN✓TRIAGED○IN PROGRESS◉CLOSED+ 1 MOREASSIGNEDCLASSIFICATION

Closed

10/11/2026, 5:48:33 PM

No activity in this phase yet.

03Sludge Pulse
stack72 assigned stack7210/11/2026, 3:39:25 PM
Editable. Press Enter to edit.

stack72 commented 10/11/2026, 4:08:53 PM

Tested on Windows Server 2022 with swamp 20261011.153055.0. The test stores SECRET_VALUE=[REDACTED-SECRET-1] via vault put (succeeds), installs the vault-roundtrip-windows model (uses WriteAllText with $env:__SWAMP_VAULT_0), then runs the model. The model run exits 1 with empty stderr. Manual testing confirms WriteAllText handles $100 correctly when set via env var (hex dump: 24 31 30 30 = $100 UTF-8). The other vault roundtrip tests (special chars, slashes, multiline, quotes, backslashes, 100KB) all pass with the same fixture. Only the $100 value causes exit 1. The resolveForPowerShell wraps the sentinel with double-quoted $env:__SWAMP_VAULT_0. Suspect the resolved value $100 is re-interpreted by PowerShell as variable $1 followed by literal 00 somewhere in the execution pipeline.

stack72 commented 10/11/2026, 4:27:05 PM

Triage update. This is classified as a bug, not a regression: this test has never passed on Windows (on 10-02 it failed on a BOM and a trailing newline from the old printf fixture, before the Windows fixture existed).

The root cause is still unknown. One point about the current theory: if the raw value did reach the PowerShell command text inside double quotes, PowerShell would expand $100 as an unset variable and get an empty string. WriteAllText would then write an empty file and exit 0, so the test would fail on the file contents, not on the exit code. The exit 1 we see points somewhere else. Code review agrees: resolveForPowerShell passes the value only through the environment, and the vault-expression splicing in model_resolver.ts inserts it by position, not with String.replace, so dollar replacement patterns can't apply there.

The missing evidence: the UAT assertion only prints stderr, so whatever error swamp reported isn't in the output we have. On the Windows host, in a fresh repo set up like the failing test (local vault test-vault, key SECRET_VALUE holding the 4-character value $100, vault-roundtrip-windows fixture installed as vault-roundtrip), please capture and post:

  1. The full stdout AND stderr of: swamp model method run vault-roundtrip execute --json
  2. The same run in log mode with --log-level debug (stdout and stderr).
  3. The result resource of that run: swamp data get vault-roundtrip result --json. Post its exitCode and stderr fields. These tell us whether PowerShell itself failed, or PowerShell exited 0 and swamp failed afterwards.
  4. The log resource: swamp data get vault-roundtrip log.
  5. Whether roundtrip-output.txt exists after the run, plus a hex dump of it if it does.
  6. As a control, the same steps with the value abc$def (a dollar sign in the middle) and with 100 (no dollar sign), so we know whether any dollar sign fails or only a leading one followed by digits.

Please paste the output as-is. The issue redactor rewrites things that look like KEY=value assignments, so describe the stored value in words rather than writing it as an assignment.

stack72 commented 10/11/2026, 4:31:10 PM

Root cause found. It is not in the model run — it is in vault put. When the value in KEY=VALUE starts with $ followed by digits, vault put stores an empty string. Control tests via Deno.Command (no shell involved): A=$100 stored as empty string. B=abc$def stored correctly as abc$def. C=100 stored correctly as 100. Only a leading $ followed by digits triggers the bug. The model run exits 1 because the resolved secret is empty and WriteAllText receives an empty value. This is likely the KEY=VALUE argument parser treating $NNN as a variable expansion (similar to shell behaviour) rather than passing the value literally. Tested on Windows binary 20261011.153055.0.

stack72 commented 10/11/2026, 5:41:36 PM

My earlier claim that vault put stores an empty string was wrong. Full diagnostic on Windows Server 2022 with binary 20261011.153055.0: (1) vault put SECRET_VALUE=[REDACTED-SECRET-1] via Deno.Command with --force: exit 0. (2) vault read-secret: value is $100, stored correctly. (3) model method run vault-roundtrip execute --json: status succeeded, exit 0. (4) roundtrip-output.txt hex dump: 24 31 30 30 = $100 correct UTF-8. The product handles $100 correctly end to end. The UAT test failure must be in the test setup. Will investigate the test code path to find where the value is lost. Apologies for the bad earlier triage.

stack72 commented 10/11/2026, 5:48:32 PM

Closing as not a product bug. Root cause was test infra: the vault-roundtrip Windows fixture (using WriteAllText) was in the top-level models/ dir and only selected by vault_roundtrip_test.ts with a manual Deno.build.os check. The security_audit_test called installFixtureModel("vault-roundtrip") which fell through to the POSIX printf fixture, causing the exit 1. Fix: moved the fixture to models/windows/vault-roundtrip.yaml so the auto-select picks it up for all callers. Confirmed passing on Windows.

Sign in to post a ripple.