Skip to content

ci: do not run fork PR code on the self-hosted runner#340

Open
evilgensec wants to merge 1 commit into
google:mainfrom
evilgensec:harden-selfhosted-fork-pr
Open

ci: do not run fork PR code on the self-hosted runner#340
evilgensec wants to merge 1 commit into
google:mainfrom
evilgensec:harden-selfhosted-fork-pr

Conversation

@evilgensec

Copy link
Copy Markdown

Problem

.github/workflows/python-app.yml runs the lint and test jobs on runs-on: self-hosted and triggers on pull_request. Both jobs check out and execute the pull request head:

  • pip install -r requirements.txt (line 40 / 74)
  • pylint ... src/gcp_scanner/*.py (line 46)
  • pytest (line 77)

On a pull_request event these jobs also run for pull requests opened from forks, with no fork guard, author-association check, or environment approval. An external contributor can open a PR that adds a test_*.py (collected by pytest), or points requirements.txt at a package with a build hook, and run arbitrary code on the self-hosted runner.

The self-hosted runner is a persistent GCE instance with an attached service account. The acceptance test (src/gcp_scanner/test_acceptance.py) scans the live project test-gcp-scanner-2 via credsdb.get_creds_from_metadata(), which reads an OAuth token from http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token. Fork PR code on that runner can read the same metadata token and exfiltrate the service-account credential, even though the job receives no secrets.*.

Fix

Add a job-level guard so the self-hosted jobs run only on pushes and on pull requests that originate from this repository (not from forks):

if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository

Pushes to main and same-repo branch PRs continue to run unchanged. Fork PRs no longer execute on the credentialed self-hosted runner.

This mirrors the guard applied to other Google self-hosted CI workflows that run on pull_request.

The lint and test jobs run on a self-hosted runner and check out and execute
the pull request head (pip install -r requirements.txt, pytest, pylint over
src/gcp_scanner/*.py). On a pull_request event this also runs for PRs from
forks, so any external contributor can execute arbitrary code on the runner.

The runner is a persistent GCE instance with an attached service account: the
acceptance test (src/gcp_scanner/test_acceptance.py) scans the live project
test-gcp-scanner-2 using credsdb.get_creds_from_metadata(), which reads an
OAuth token from the instance metadata server. Fork PR code on that runner can
read the same metadata token and steal the service-account credential, even
though no secrets are passed to the job.

Guard both self-hosted jobs so they run only on pushes and on pull requests
originating from this repository, not from forks.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant