What 2026's GitHub supply-chain incidents show about unlocked releases
Two 2026 incidents took different routes toward the same prize: access that can change a project's releases. In one, the OVHcloud CLI, release files were swapped. In the other, a campaign Radware found, attackers tried to steal CI secrets through GitHub issues and comments, and Radware has not confirmed any swap. Either way, immutable releases stop the last step. For what each lock level means, see Can this GitHub release be swapped?
Short answer
Anyone who gets credentials that can write to a repository can replace the files on a release, unless the release is immutable. A stolen maintainer account is one way to get those credentials. A workflow that runs text from issues, pull requests or comments as code is another possible way. Locking releases, cutting token permissions and handling outside text safely each break a different step.
Path A: a stolen account replaces release files (OVHcloud CLI, October 19, 2026)
According to OVHcloud's advisory GHSA-862f-55f5-vmvw, a compromised OVHcloud GitHub
developer account was used to replace existing release assets on ovh/ovhcloud-cli versions
v0.11.0 through v0.15.0 with malicious binaries. The window ran from 14:47 to 16:21 UTC+2 (5:47 to 7:21 AM PT),
and OVHcloud puts the malware's availability at 1 hour 33 minutes, with about 250 downloads. Source code and
the build chain were not affected.
OVHcloud named the root cause: the repository was not configured to ensure release immutability. It fixed
that setting on every repository and published a clean v0.16.0 with a signed tag. The customer report in
issue #300 shows that checksums.txt was replaced along with the archives,
and that the problem was caught because a CI job had pinned one archive's SHA-256 from earlier.
The first learn page covers this incident in more detail.
Path B: issue and comment text injected into build jobs (Radware campaign, September 1, 2026)
What happened
Radware researchers, in their write-up and in an interview with The Register published October 29, 2026, describe 83 malicious issues, pull requests and comments across 61 public repositories, all posted on September 1, 2026. 77 of them went up in about 43 minutes, between 11:26 and 12:10 UTC (4:26 to 5:10 AM PT), and six more came in a second wave. They came from 74 GitHub accounts, many with real development history, which means many were likely compromised. Each item carried a shell command, often hidden inside an HTML comment so it did not show on the rendered page. The command sat in the title in 31 cases, in the body in 17, and in a comment in 35.
How the injection works
Some workflows run automatically when someone opens an issue, opens a pull request or posts a comment. If the
workflow builds a shell line like ISSUE_TITLE="${{ github.event.issue.title }}", GitHub fills in
the title before the shell runs. A $() command inside the title then runs as code.
GitHub's docs on script injections describe exactly this risk and list titles, bodies
and comments as untrusted input.
In this campaign, the injected command gathered the job's environment variables, where secrets and tokens often live, and tried to send them to a server the attacker controlled. Radware notes the attacker needed no write access to start. Opening an issue or a pull request can be enough, and the workflow fires within seconds, whether or not anyone ever merges or answers the item.
The unsafe pattern pastes the text into the script:
# Unsafe: the title becomes part of the shell script
on:
issues:
types: [opened]
jobs:
notify:
runs-on: ubuntu-latest
steps:
- run: |
echo "New issue: ${{ github.event.issue.title }}"
The safer pattern, from GitHub's secure use reference, passes the text through an environment variable and quotes it:
# Safer: the title stays data, not code
steps:
- env:
TITLE: ${{ github.event.issue.title }}
run: |
echo "New issue: $TITLE"
What Radware did and did not confirm
Confirmed: run logs showed the malicious command executed inside GitHub Actions in two cases.
Not established: Radware says it did not establish that the attacker obtained repository write permissions, modified source code or compromised downstream software. Its supply-chain scenario describes possible consequences, not outcomes it verified.
The researchers told maintainers of every targeted project. The Register reports most of the repositories were closed or fixed, but the researchers could not confirm all of them. Coverage named well-known projects as targets. Being targeted is not the same as being compromised, and no named project has been shown to have lost access or had a release changed.
From stolen token to swapped release: the possible path
Here is why this matters for releases. If a leaked secret or GITHUB_TOKEN had permission
to write repository contents, then it could be used to upload or replace files on a release that is not
immutable. That would be the same end state as Path A: same version name, different bytes. Radware's Gabi
Nakibly told The Register the team's guess is that the final aim was a supply-chain attack using the repo's
credentials. That is a suspected aim, not a confirmed result.
Which control stops which step
| Attack step | Path | Control that breaks it |
|---|---|---|
| Outside text reaches a shell step | B | Pass untrusted values through an env: variable. Never put ${{ github.event.* }} straight into run:. (script injections, secure use) |
| Injected command reads secrets | B | Keep secrets and write tokens out of jobs triggered by issues, comments and pull requests. Separate those jobs from privileged ones. Avoid pull_request_target and workflow_run with untrusted content. (secure use) |
| Stolen token can write to the repo | A, B | Give GITHUB_TOKEN the least access it needs, and grant write access per job only when needed. (secure use, GITHUB_TOKEN docs) |
| Release files replaced after publish | A, B | Immutable releases: assets and tag are locked once published, and GitHub generates a release attestation. (immutable releases) |
| Downloader gets swapped bytes | A, B | Pin a hash you got earlier or from another channel. Check with gh release verify and gh release verify-asset. Look up the tool's lock level on ReleaseLock. (verify docs, issue #300) |
One limit to keep in mind: immutable releases do not stop secret theft or a poisoned build before publish. They stop files from being replaced after the fact.
What maintainers should do this week
- Search your workflow files for
${{ github.event.insiderun:blocks, and move those values intoenv:variables. - Look closely at workflows triggered by
issues,issue_comment,pull_request_targetandworkflow_run. See GitHub's list of workflow triggers. - Set the default
GITHUB_TOKENpermissions to read only, and add write access only to the jobs that need it. - If a workflow was injectable, treat every secret and token that job could see as exposed and rotate it.
- Turn on immutable releases for the repository or organization. Publish as a draft, attach every file, then publish, as GitHub recommends.
- To catch unlocked or unpinned Actions in your own workflows, add the free ReleaseLock gate.
Radware's advice, as reported by The Register, points the same way: give CI the least access it needs, keep workflows triggered by outside events apart from jobs with elevated permissions, treat outside content as untrusted, and limit the secrets a workflow can see. GitHub Security Lab's guide to untrusted input goes deeper.
What downloaders should do
- Pin hashes in CI for the release files you install, from a copy you already trust.
- Prefer releases that are immutable or attested.
- Treat a checksums file on the same release as a check for broken downloads, not proof against a swap. Here is why.
- Look the tool up in the ReleaseLock directory before adding it to a default install path.
This is not a rare gap. In independent research published October 24, 2026, Xianpeng Shen checked the latest release of 242 popular Go and Rust CLI projects and found that for 179 of them, the files could be replaced and a downloader would have no way to tell. Those numbers are his, not ReleaseLock's.
Incident log
We add new incidents here as they are reported, newest first.
- October 29, 2026: Radware's GitHub issue and comment campaign reported (activity on September 1, 2026). Path B. Command execution confirmed in two cases. No release swap confirmed.
- October 19, 2026: OVHcloud CLI release files replaced on v0.11.0 through v0.15.0 using a compromised developer account. Path A. Swap confirmed by OVHcloud.
Related on ReleaseLock
- Can this GitHub release be swapped? Lock levels explained.
- Search the directory for a tool's lock level.
- Public API to check one release:
GET /v1/verify/{owner}/{repo}/{tag} - Add the gate to your org to scan workflows in CI.
- Newsletter
Quick answers
Can a GitHub issue or PR comment run code in a project's Actions workflow?
Yes, if the workflow pastes issue, pull request or comment text straight into a shell step. GitHub fills in expressions like ${{ github.event.issue.title }} before the shell runs, so a $() command in the title becomes code. GitHub's docs say to treat that text as untrusted and pass it through an environment variable instead. In a campaign Radware found on September 1, 2026, attackers posted 83 such items across 61 public repositories to try to steal CI secrets.
Did the September 2026 GitHub issue campaign swap any releases?
No swap has been confirmed. Radware found evidence the injected command ran in two cases, but said it did not establish that the attacker got repository write access, changed source code or compromised downstream software. The risk is the path: a stolen token with write access to repository contents could replace files on a release that is not immutable.
What actually stops a GitHub release swap?
Immutable releases. Once an immutable release is published, GitHub blocks changes to its assets and blocks moving or deleting its tag. That stops after-the-fact replacement whether the attacker used a stolen account, as in the OVHcloud CLI incident on October 19, 2026, or a stolen CI token. Downloaders can add a second layer by pinning hashes and checking a tool's lock level on ReleaseLock.
Do immutable releases stop secret theft or a poisoned build?
No. Immutable releases lock a release after it is published. They do not stop someone from stealing secrets from a workflow, and they do not prove the build itself was clean. They stop the files and tag of a published release from being changed later.
Look it up in the directory, or add the free scan step to your workflows.
Search the directorySources
- Radware, Inside a GitHub supply chain attack campaign
- The Register, Jessica Lyons, October 29, 2026, Attackers turn GitHub issues into supply chain campaign against 61 public repos in less than an hour
- OVHcloud, ovhcloud-cli security incident, October 19th, 2026 (GHSA-862f-55f5-vmvw)
- ovh/ovhcloud-cli, issue #300: release assets of v0.13.0 to v0.15.0 replaced on 2026-10-19
- Xianpeng Shen, October 24, 2026, After OVHcloud's release was swapped: three in four popular CLIs could be too
- GitHub Docs, Script injections
- GitHub Docs, Secure use reference
- GitHub Docs, Use GITHUB_TOKEN for authentication in workflows
- GitHub Docs, Events that trigger workflows
- GitHub Docs, Immutable releases
- GitHub Docs, Preventing changes to your releases
- GitHub Docs, Verifying the integrity of a release
- GitHub Security Lab, Keeping your GitHub Actions and workflows secure Part 2: Untrusted input