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/kerberos repository
Tested Version
Observed on the default branch at commit 445408e9fa5789b71bfdcef37f7c0ccda5dc5126.
Details
Mutable pull request checkout enables privileged code execution with a write-scoped GITHUB_TOKEN (GHSL-2026-191)
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 workflow invokes mongodb-labs/drivers-github-tools/node/setup@v3 with ignore_install_scripts: true. This causes the setup action to run npm clean-install --ignore-scripts, preventing ordinary package lifecycle scripts from executing during installation. However, it does not make the checkout trusted.
The workflow subsequently executes pull request-controlled pr_list.mjs, highlights.mjs, and release_notes.mjs. Each process receives the write-scoped target-repository GITHUB_TOKEN through 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 the contributor’s new, unauthorised commit.
- Modified repository scripts execute with
GITHUB_TOKENin their environment despite package lifecycle scripts being disabled.
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-191 in any communication regarding this issue.