Coordinated Disclosure Timeline
Summary
The release_notes workflow authorizes a maintainer to trigger a run, but does not bind that authorization to the commit that the maintainer reviewed. It subsequently checks out the mutable refs/pull/<number>/head ref and executes files from that checkout. An external pull request author can update the ref after the maintainer’s decision and have attacker-controlled code execute in a job whose GITHUB_TOKEN has contents: write and pull-requests: write permissions.
Project
mongodb-js/mongodb-connection-string-url repository
Tested Version
Observed on the default branch at commit f145365bae7fe169d33a1d871cdd64d9b33cd8a0.
Details
Mutable pull request checkout enables privileged code execution with a write-scoped GITHUB_TOKEN (GHSL-2026-190)
The release_notes workflow can be started either manually with a pull request number or by posting run release_notes on a pull request. The job is granted the following permissions:
permissions:
contents: write
pull-requests: write
The workflow checks whether github.triggering_actor has maintain permission. This establishes that the person starting or rerunning the job is trusted, but it does not identify or record the pull request commit that person intended to authorize. After the check, the workflow checks out a mutable pull request ref:
- name: check if triggering_actor is allowed to generate notes
env:
GITHUB_TOKEN: ${{ github.token }}
COMMENTER: ${{ github.triggering_actor && github.triggering_actor || 'empty_triggering_actor' }}
API_ENDPOINT: /repos/${{ github.repository }}/collaborators?permission=maintain
# ...verify that COMMENTER is a maintainer...
- uses: actions/checkout@v6
with:
ref: refs/pull/${{ github.event_name == 'issue_comment' && github.event.issue.number || inputs.releasePr }}/head
An external pull request author controls this ref and can update it after review, after the trigger command, or while the run is queued. actions/checkout resolves whatever commit the ref points to when the checkout step executes, rather than a commit bound to the maintainer’s authorization.
The newly checked-out tree reaches two code-execution paths:
- The workflow invokes the checkout-relative
./.github/actions/setup. The pull request author controls the local composite action definition, which runs shell commands andnpm ciwith package lifecycle scripts enabled against the pull request checkout. - The workflow directly executes pull request-controlled
pr_list.mjs,highlights.mjs, andrelease_notes.mjs. Each process receives the write-scoped target-repositoryGITHUB_TOKENthrough its environment.
Consequently, a pull request author without target-repository write access can cross the trust boundary from controlling only their pull request branch to running code with the target repository’s token authority.
Attack scenario
The minimum attacker is an external contributor who controls the head branch of an open pull request selected for release-note generation.
- The contributor presents a benign commit for review.
- A maintainer dispatches the workflow for that pull request or posts the accepted
run release_notescomment. - The contributor updates the pull request head before
actions/checkoutresolvesrefs/pull/<number>/head. - The maintainer passes the authorization check, but the workflow checks out and executes the contributor’s new, unauthorised commit.
- A modified local action or package lifecycle script executes during setup, and modified repository scripts subsequently execute with
GITHUB_TOKENin their environment.
The attacker may use a script such as ActionsTOCTOU to automate updating the pull request after the triggering comment or workflow_dispatch run and win the TOCTOU race.
Impact
This issue enables arbitrary code execution on a GitHub Actions runner in a privileged target-repository workflow. Attacker-controlled JavaScript receives a GITHUB_TOKEN with contents: write and pull-requests: write, allowing it to alter repository content or pull request state within the token’s permissions and the repository’s branch-policy constraints.
A maintainer must trigger the workflow, and branch protection may restrict changes to protected refs. The report does not claim a branch-protection bypass; the privilege gain is access to the target repository token and its granted write permissions.
CWEs
- CWE-367: Time-of-check Time-of-use (TOCTOU) Race Condition
Credit
This issue was discovered and reported by GHSL team member @JarLob (Jaroslav Lobačevski).
Contact
You can contact the GHSL team at securitylab@github.com. Please include a reference to GHSL-2026-190 in any communication regarding this issue.