mirror of
https://github.com/github/codeql.git
synced 2026-04-28 18:25:24 +02:00
JS: Remove js/actions/pull-request-target
Superseded by actions/untrusted-checkout/{medium,high,critical}
This commit is contained in:
@@ -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>
|
||||
@@ -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'."
|
||||
@@ -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!'
|
||||
});
|
||||
@@ -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!
|
||||
@@ -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/
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -1,10 +0,0 @@
|
||||
on:
|
||||
pull_request_target:
|
||||
|
||||
jobs:
|
||||
echo-chamber:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: master
|
||||
@@ -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
|
||||
@@ -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 }}
|
||||
@@ -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'. |
|
||||
@@ -1 +0,0 @@
|
||||
experimental/Security/CWE-094/UntrustedCheckout.ql
|
||||
Reference in New Issue
Block a user