JS: Remove js/actions/pull-request-target

Superseded by actions/untrusted-checkout/{medium,high,critical}
This commit is contained in:
Asger F
2025-06-23 14:37:21 +02:00
parent 0d3bb89195
commit 3a00e8d1c5
17 changed files with 0 additions and 425 deletions

View File

@@ -1,64 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Combining <i>pull_request_target</i> workflow trigger with an explicit checkout
of an untrusted pull request is a dangerous practice
that may lead to repository compromise.
</p>
</overview>
<recommendation>
<p>
The best practice is to handle the potentially untrusted pull request
via the <i>pull_request</i> trigger so that it is isolated in
an unprivileged environment. The workflow processing the pull request
should then store any results like code coverage or failed/passed tests
in artifacts and exit. The following workflow then starts on <i>workflow_run</i>
where it is granted write permission to the target repository and access to
repository secrets, so that it can download the artifacts and make
any necessary modifications to the repository or interact with third party services
that require repository secrets (e.g. API tokens).
</p>
</recommendation>
<example>
<p>
The following example allows unauthorized repository modification
and secrets exfiltration:
</p>
<sample src="examples/pull_request_target_bad.yml" />
<p>
The following example uses two workflows to handle potentially untrusted
pull request in a secure manner. The receive_pr.yml is triggered first:
</p>
<sample src="examples/receive_pr.yml" />
<p>The comment_pr.yml is triggered after receive_pr.yml completes:</p>
<sample src="examples/comment_pr.yml" />
</example>
<references>
<li>GitHub Security Lab Research: <a href="https://securitylab.github.com/research/github-actions-preventing-pwn-requests">Keeping your GitHub Actions and workflows secure: Preventing pwn requests</a>.</li>
</references>
</qhelp>

View File

@@ -1,81 +0,0 @@
/**
* @name Checkout of untrusted code in trusted context
* @description Workflows triggered on `pull_request_target` have read/write access to the base repository and access to secrets.
* By explicitly checking out and running the build script from a fork the untrusted code is running in an environment
* that is able to push to the base repository and to access secrets.
* @kind problem
* @problem.severity warning
* @precision low
* @id js/actions/pull-request-target
* @tags actions
* security
* experimental
* external/cwe/cwe-094
*/
import javascript
import semmle.javascript.Actions
/**
* An action step that doesn't contain `actor` check in `if:` or
* the check requires manual analysis.
*/
class ProbableStep extends Actions::Step {
// some simplistic checks to eleminate likely false positives:
ProbableStep() {
// no if at all
not exists(this.getIf().getValue())
or
// needs manual analysis if there is OR
this.getIf().getValue().matches("%||%")
or
// actor check means only the user is able to run it
not exists(this.getIf().getValue().regexpFind("\\bgithub\\s*\\.\\s*actor\\s*==", _, _))
}
}
/**
* An action job that doesn't contain `actor` check in `if:` or
* the check requires manual analysis.
*/
class ProbableJob extends Actions::Job {
// some simplistic checks to eleminate likely false positives:
ProbableJob() {
// no if at all
not exists(this.getIf().getValue())
or
// needs manual analysis if there is OR
this.getIf().getValue().matches("%||%")
or
// actor check means only the user is able to run it
not exists(this.getIf().getValue().regexpFind("\\bgithub\\s*\\.\\s*actor\\s*==", _, _))
}
}
/**
* The `on: pull_request_target`.
*/
class ProbablePullRequestTarget extends Actions::On, YamlMappingLikeNode {
ProbablePullRequestTarget() {
// The `on:` is triggered on `pull_request_target`
exists(this.getNode("pull_request_target"))
}
}
from
Actions::Ref ref, Actions::Uses uses, Actions::Step step, Actions::Job job,
ProbablePullRequestTarget pullRequestTarget
where
pullRequestTarget.getWorkflow() = job.getWorkflow() and
uses.getStep() = step and
ref.getWith().getStep() = step and
step.getJob() = job and
uses.getGitHubRepository() = "actions/checkout" and
ref.getValue()
.matches([
"%github.event.pull_request.head.ref%", "%github.event.pull_request.head.sha%",
"%github.event.pull_request.number%", "%github.event.number%", "%github.head_ref%"
]) and
step instanceof ProbableStep and
job instanceof ProbableJob
select step, "Potential unsafe checkout of untrusted pull request on 'pull_request_target'."

View File

@@ -1,52 +0,0 @@
name: Comment on the pull request
# read-write repo token
# access to secrets
on:
workflow_run:
workflows: ["Receive PR"]
types:
- completed
jobs:
upload:
runs-on: ubuntu-latest
if: >
${{ github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success' }}
steps:
- name: 'Download artifact'
uses: actions/github-script@v3.1.0
with:
script: |
var artifacts = await github.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{github.event.workflow_run.id }},
});
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "pr"
})[0];
var download = await github.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
var fs = require('fs');
fs.writeFileSync('${{github.workspace}}/pr.zip', Buffer.from(download.data));
- run: unzip pr.zip
- name: 'Comment on PR'
uses: actions/github-script@v3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
var fs = require('fs');
var issue_number = Number(fs.readFileSync('./NR'));
await github.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue_number,
body: 'Everything is OK. Thank you for the PR!'
});

View File

@@ -1,25 +0,0 @@
on:
pull_request_target
jobs:
build:
name: Build and test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/setup-node@v1
- run: |
npm install
npm build
- uses: completely/fakeaction@v2
with:
arg1: ${{ secrets.supersecret }}
- uses: fakerepo/comment-on-pr@v1
with:
message: |
Thank you!

View File

@@ -1,26 +0,0 @@
name: Receive PR
# read-only repo token
# no access to secrets
on:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# imitation of a build process
- name: Build
run: /bin/bash ./build.sh
- name: Save PR number
run: |
mkdir -p ./pr
echo ${{ github.event.number }} > ./pr/NR
- uses: actions/upload-artifact@v2
with:
name: pr
path: pr/

View File

@@ -1,38 +0,0 @@
on:
pull_request_target:
jobs:
job1:
if: contains(github.event.issue.labels.*.name, 'ok')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}
job2:
if: github.event.label.name == 'ok'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}
job3:
if: github.actor == 'ok'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}
job4:
if: github.actor == 'ok' || true
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}
job5:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}

View File

@@ -1,31 +0,0 @@
on:
pull_request_target:
jobs:
job1:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
if: contains(github.event.issue.labels.*.name, 'ok')
with:
ref: ${{ github.event.pull_request.head.ref }}
- uses: actions/checkout@v2
if: github.event.label.name == 'ok'
with:
ref: ${{ github.event.pull_request.head.ref }}
- uses: actions/checkout@v2
if: github.actor == 'ok'
with:
ref: ${{ github.event.pull_request.head.ref }}
- uses: actions/checkout@v2
if: github.actor == 'ok' || true
with:
ref: ${{ github.event.pull_request.head.ref }}
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}

View File

@@ -1,12 +0,0 @@
on:
pull_request_target:
types: [labeled]
push:
jobs:
echo-chamber:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}

View File

@@ -1,13 +0,0 @@
on:
pull_request_target:
types:
labeled:
push:
jobs:
echo-chamber:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}

View File

@@ -1,15 +0,0 @@
on:
pull_request_target:
types:
labeled:
opened:
closed:
push:
jobs:
echo-chamber:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}

View File

@@ -1,12 +0,0 @@
on:
pull_request_target:
types: [labeled, opened]
push:
jobs:
echo-chamber:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}

View File

@@ -1,10 +0,0 @@
on:
pull_request_target:
jobs:
echo-chamber:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}

View File

@@ -1,10 +0,0 @@
on:
pull_request_target:
jobs:
echo-chamber:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: master

View File

@@ -1,11 +0,0 @@
on: pull_request_target
jobs:
echo-chamber:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}
- run: make

View File

@@ -1,9 +0,0 @@
on: [pull_request_target, push]
jobs:
echo-chamber:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}

View File

@@ -1,15 +0,0 @@
| .github/workflows/pull_request_target_if_job.yml:9:7:12:2 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
| .github/workflows/pull_request_target_if_job.yml:16:7:19:2 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
| .github/workflows/pull_request_target_if_job.yml:30:7:33:2 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
| .github/workflows/pull_request_target_if_job.yml:36:7:38:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
| .github/workflows/pull_request_target_if_step.yml:9:7:14:4 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
| .github/workflows/pull_request_target_if_step.yml:14:7:19:4 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
| .github/workflows/pull_request_target_if_step.yml:24:7:29:4 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
| .github/workflows/pull_request_target_if_step.yml:29:7:31:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
| .github/workflows/pull_request_target_label_only.yml:10:7:12:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
| .github/workflows/pull_request_target_label_only_mapping.yml:11:7:13:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
| .github/workflows/pull_request_target_labels_mapping.yml:13:7:15:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
| .github/workflows/pull_request_target_labels_sequence.yml:10:7:12:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
| .github/workflows/pull_request_target_mapping.yml:8:7:10:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
| .github/workflows/pull_request_target_run.yml:7:7:11:4 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
| .github/workflows/pull_request_target_sequence.yml:7:7:9:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |

View File

@@ -1 +0,0 @@
experimental/Security/CWE-094/UntrustedCheckout.ql