fix/security: shell-escape attacker-controlled filenames in batch change templates#1332
Open
cbrnrd wants to merge 1 commit into
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note: this issue was brought to our attention via a HackerOne report.
Problem
src-clirenders batch specrun:fields through Go'stext/template, which performs no output escaping, and thejoinbuiltin is a plainstrings.Join. The rendered string is written to a script file and executed under/bin/shinside the batch-change container.Several template variables expose raw filenames parsed from
git diffoutput and from Sourcegraph search results:repository.search_result_pathssteps.{modified,added,deleted,renamed}_filesprevious_step.{modified,added,deleted,renamed}_filesstep.{modified,added,deleted,renamed}_filesBecause git permits filenames containing shell metacharacters (
`,$,;,|,&,>,<, newlines, …), an attacker who controls filenames in a target repository can inject arbitrary shell commands into the rendered script. The reproducer in the report uses the canonicalgofmt -w ${{ join repository.search_result_paths " " }}example from the Batch Changes docs to exfiltrateSRC_ACCESS_TOKENand tamper with the workspace.This is a supply-chain attack vector: planting a maliciously named file in a public repo is enough to trigger code execution in any organization that runs a batch change matching it. The container has the workspace mounted read-write plus access to
SRC_ACCESS_TOKENand anyenv:secrets.Solution
Shell-escape every filename-bearing value at the template
FuncMapboundary usinggithub.com/kballard/go-shellquote, so the rendered text is always safe to splat into/bin/shregardless of what the underlying spec author wrote.Changes in
lib/batches/template/templating.go:Added a
shellEscapeAll([]string) []stringhelper that runsshellquote.Joinover each element. Elements without metacharacters pass through unmodified, so existing specs targeting benign filenames keep producing identical output.Applied it to the four
*_filesfields in bothStepContext.ToFuncMap(coversstep,previous_step,steps) andChangesetTemplateContext.ToFuncMap(covers changeset templates).Pre-escaped each element of
Repository.SearchResultPaths()so${{ repository.search_result_paths }}and${{ join repository.search_result_paths " " }}are both safe.Kept the slice shape (
[]string) rather than collapsing into a pre-joined string, so${{ join … " " }}and${{ range … }}keep working unchanged for spec authors.Left
stdout/stderrraw. They are routinely captured intooutputsand reused as plain values (e.g. a filename written byecho); pre-quoting silently changes those semantics. Spec authors who splat stdio against untrusted data should opt in with the newshellquote_jointemplate builtin:This trade-off is documented inline next to the assignment.
The fix is purely at the template layer; the
git diffparser inlib/batches/git/changes.gois intentionally left alone so unusual-but-legitimate filenames aren't silently dropped.Verification Evidence
Regression test
Added
lib/batches/template/templating_security_test.go(TestVULN91_NoShellInjectionFromFilenames). It feeds the report's exact PoC filenamesthrough every previously vulnerable variable in both
StepContextandChangesetTemplateContext, and asserts that none of`id,$(,; echo,> /tmp/PWNED, orPWNED2appear outside a single-quoted shell region in the rendered output.Regression test catches the unpatched code
Reverting just the
shellEscapeAll(res.ChangedFiles.Modified)call to its originalres.ChangedFiles.Modifiedand re-running:i.e. the test loudly reproduces the exact injection from the HackerOne report on unpatched code, and passes on the patched code.
Full test suite
Pre-existing executor tests that exercise
${{ join previous_step.modified_files " " }}and${{ previous_step.modified_files }}continue to render the same output for benign filenames (shellquote.Join("modified.txt")→modified.txt), so no batch spec authored against the documented examples sees a behavior change.Test Plan
TestVULN91_NoShellInjectionFromFilenamescovers every affected variable on bothStepContextandChangesetTemplateContext.go test ./...passes for both modules.