fix: escape shell metacharacters in expanded env files#1311
Conversation
Site previewPreview: https://290e0d47-site.fullsend-ai.workers.dev Commit: |
ReviewFindingsNo findings. Previous runReviewFindingsNo findings. The change correctly replaces The test suite is thorough: 21 unit tests cover individual escape cases, edge cases, and injection attempts; 28 shell roundtrip tests source the expanded file in a real The remaining |
Replace os.ExpandEnv with a shell-safe expander that escapes the four
characters special inside double quotes (\, ", $, `) before substituting
values into env file templates. This prevents user-authored text like
HUMAN_INSTRUCTION from breaking shell syntax when the expanded env file
is sourced inside the sandbox.
The env file templates use export FOO="${FOO}" — after Go-level expansion,
unescaped values containing double quotes or parentheses would break out
of the quoting context, causing "Syntax error: ( unexpected" or similar
failures in the sandbox shell.
Includes 21 unit tests and 28 shell roundtrip tests that verify every
expanded value survives a real sh source-and-read cycle, covering
injection attempts, all special characters, and the exact values from
the failing runs referenced in the issues.
Fixes #408
Fixes #615
Signed-off-by: Ralph Bean <[email protected]>
Assisted-by: Claude claude-opus-4-6 <[email protected]>
Signed-off-by: Ralph Bean <[email protected]>
2c08535 to
f2c64ba
Compare
waynesun09
left a comment
There was a problem hiding this comment.
Review Squad (10 agents) — 1 HIGH, 2 MEDIUM findings
Well-executed security fix. The escaping logic is POSIX-correct (all four double-quote special characters handled in the right order) and the shell roundtrip tests are exemplary. The HIGH finding — 9 env template lines across 5 files use unquoted ${VAR} patterns where the escaping assumptions don't hold — is not exploitable today (all values are machine-generated), but the templates should be standardized to "${VAR}" in a follow-up to eliminate the risk class.
| // shellSafeExpandEnv expands ${VAR} references in text using the host | ||
| // environment, escaping characters that are special inside double quotes | ||
| // (", $, `, \) so the result is safe to source as a shell script. | ||
| // Templates use the standard export FOO="${FOO}" pattern; this function | ||
| // ensures substituted values cannot break out of the double-quote context. | ||
| // Fixes #408, #615. |
There was a problem hiding this comment.
HIGH — Unquoted env templates are not protected by this escaping
The doc comment says "Templates use the standard export FOO="${FOO}" pattern" but 9 template lines across 5 files use unquoted ${VAR}:
code-agent.env:export ISSUE_NUMBER=${ISSUE_NUMBER},GITHUB_ISSUE_URL,GH_TOKENreview.env,triage.env,prioritize.env:export GH_TOKEN=${GH_TOKEN}gcp-vertex.env:ANTHROPIC_VERTEX_PROJECT_ID,CLOUD_ML_REGION,GOOGLE_CLOUD_PROJECT
In an unquoted context, characters like spaces, ;, |, &&, > are shell operators — the escaper does NOT protect against them. Currently safe because these are machine-generated values (tokens, integers, project IDs), but the function name shellSafeExpandEnv implies broader safety than it provides.
Suggestion (follow-up PR): Standardize all env templates to export VAR="${VAR}" — a one-line change per template, zero regression risk (double-quoting simple values is a no-op in shell), eliminates an entire class of future bugs.
7/10 review agents flagged this (strong consensus)
| env: map[string]string{"A": "1", "B": "two (2)"}, | ||
| want: "export A=\"1\"\nexport B=\"two (2)\"", | ||
| }, | ||
| { |
There was a problem hiding this comment.
MEDIUM — Misleading test name
This test uses FOO=bar (no special characters) but the name "unquoted template still safe" implies the escaper handles unquoted contexts safely for arbitrary values. It does not — e.g., FOO="hello world" would expand to export FOO=hello world, and sh would interpret world as a separate command.
Suggest renaming to "unquoted template with simple value" to remove the safety implication, and optionally adding a comment documenting that unquoted templates are only safe for values without whitespace or shell operators.
5/10 review agents flagged this
| @@ -430,6 +431,209 @@ func TestEnvToList_Sorted(t *testing.T) { | |||
| assert.Equal(t, "Z_VAR=z", list[2]) | |||
| } | |||
|
|
|||
There was a problem hiding this comment.
MEDIUM — Missing test for $VAR (no braces) expansion
All test templates use ${VAR} (with braces), but os.Expand also handles $VAR (without braces). Adding one test case with template export FOO="$FOO" would confirm the brace-less path also gets escaped values and document this behavior.
3/10 review agents flagged this
Summary
os.ExpandEnvwith a shell-safe expander that escapes\,",$, and`— the four characters special inside double-quoted shell stringsexport FOO="${FOO}") stay unchanged; the expander ensures substituted values can't break out of the double-quote contextsh, read value back, assert exact match)Problem
HUMAN_INSTRUCTIONand other user-authored env vars are expanded into shell env files viaos.ExpandEnv, which does naive text substitution. When the value contains",(,$, or backticks, the expanded file has invalid shell syntax:This caused the pre-agent security scan to flag it as a critical finding, blocking the fix agent entirely.
Failing run: https://github.com/fullsend-ai/.fullsend/actions/runs/26232380969/job/77196067764
Fix
shellSafeExpandEnvusesos.Expandwith a custom mapper that callsescapeForDoubleQuoteson each substituted value. Only the four characters that have special meaning inside double quotes need escaping:\\\"\"$\$`\`Characters like
(,),||,&&,;,>are not special inside double quotes and don't need escaping.Test plan
sh, and assert the value matches the original — covering injection attempts ("; rm -rf /; echo "), command substitution ($(evil)), and the exact values from the failing runsgo test ./internal/cli/passesgo vet ./internal/cli/cleanmake lintcleanFixes #408
Fixes #615