Detect and clean up after the PolinRider supply-chain campaign: GitHub organizations, personal accounts and macOS, Linux and Windows machines. One binary, dry-run by default.
See the code
Detect and clean up after the PolinRider supply-chain campaign, on a developer machine, in a folder of code, on a GitHub organization or on a personal account.
One program. Nothing else to install. It changes nothing until you type yes.
[!CAUTION] Mid-incident? Download, verify, run. It asks what you need, one question at a time, and tells you what it found in plain words before it offers to change anything.
Download the archive for your machine from the latest release: macOS (Apple silicon or Intel), Linux (x86_64 or arm64) or Windows (x86_64).
Verify it before running it. This is a tool for recovering from tampered code, so take it at its word about nothing:
sha256sum -c SHA256SUMS --ignore-missing
gh attestation verify polinrider-v2.1.0-macos-arm64.tar.gz --repo meSingh/polinrider-cleaner
Unpack and run it, with no arguments:
tar -xzf polinrider-v2.1.0-macos-arm64.tar.gz
cd polinrider-v2.1.0-macos-arm64
./polinrider
On macOS the binary is not notarised, so Gatekeeper stops the first run.
Once you have verified it: xattr -d com.apple.quarantine ./polinrider.
Keep the ioc/ folder beside the binary. That is the indicator set, and the
binary refuses to scan without it.
Or through a package manager, which puts the binary and ioc/ in place:
brew install meSingh/tap/polinrider-cleaner # macOS and Linux
scoop bucket add mesingh https://github.com/meSingh/scoop-bucket
scoop install polinrider-cleaner # Windows
Since 2.1.0, releases also carry a .deb for Debian and Ubuntu, x86_64 and
arm64, attested like the archives. Verify it as above, then:
sudo apt install ./polinrider-vX.Y.Z-linux-x86_64.deb
A winget package is being prepared and is not in the winget repository yet.
Every release since 2.0, with what it changes and downloads for each system, is on the Releases page.
1.x, the shell tool, is frozen and still available from v1.0.9.
The first screen asks what to check. Answers are words, never numbers.
| Answer | What it checks |
|---|---|
computer | This machine: your whole home folder, plus login items, scheduled jobs, shell startup files, git and npm settings, running programs and open connections |
folder | One folder of code. It suggests the code folders it finds |
organization | Every repository, branch and tag of a GitHub organization you belong to |
account | The same for your own GitHub account |
everything | This computer first, then GitHub, one after the other |
It shows how far it has come while it works. Then a short summary in plain
words, with details for the full list, and one question about what to do.
It changes something only on a typed yes. q leaves. Input that ends
stops the session. Every run saves a full report in your home folder and names
it on the last screen.
On GitHub, for each infected repository it offers what can really fix it:
| Choice | What it does |
|---|---|
restore | Moves each branch back to the newest clean state on GitHub's own push record. Offered only where that record shows one. Never trusts commit dates, which this malware forges |
erase | Rewrites history so the payload is in no commit, then shows how to reset every existing clone |
remove | One new commit that takes the payload out. History is left as it is |
archive | Puts a large infected notice at the top of the README, rewrites the description and makes the repository read-only. Removes nothing |
skip | Leaves it |
Each one shows a dry run of exactly what would change on GitHub before it asks.
all applies one fix to every repository that can take it, behind a warning,
and goes ahead only when you type the organization's name.
GitHub checks need gh installed and signed in. It
checks that first and tells you what to run if not.
polinrider check ~/code # read-only scan of this machine and that folder
polinrider check --fs-only ~/code # only the folder, nothing about this machine
polinrider check --apply ~/code # move confirmed files into quarantine. Never deletes
polinrider clean ~/code/shop # what it would cut out of an infected build config
polinrider clean --apply ~/code/shop
polinrider --help
clean strips a payload appended to a build config in place and keeps the
infected original in quarantine. It never touches git: nothing is staged,
committed, reset or stashed.
| Exit code | Meaning |
|---|---|
0 | Clean against the current indicator set |
1 | Review items only |
2 | A confirmed indicator |
3 | The scan could not run. Nothing was checked |
A clean result means the current indicators were not found. It is not a certificate.
The tool covers steps 1 and 3. Step 2 is yours, and it is the one that matters most.
Run polinrider and answer computer on every machine that has touched the
affected repositories. The verdict is followed by what to do on that machine.
Sources disagree, so here is the rule this repository uses.
The tool says "rebuild" only when it has found proof that the payload ran on that machine. Either way, credential rotation is not optional.
Assume everything reachable from the affected user account is in someone else's hands. Do this from a machine you trust, before restoring any branch.
[!IMPORTANT] If a crypto wallet or seed phrase was on that machine, move the funds now. This malware targets them specifically.
First, the ones that grant repository write:
gh api /repos/OWNER/REPO/keysSecond, publish rights:
npm token list for tokens you did not createThird, everything else the stealer could read:
~/.aws/credentials, ~/.config/gcloud, service account keys.env on the affected machineCached git credentials survive a password change. Clear them:
# macOS
git credential-osxkeychain erase <<< $'protocol=https\nhost=github.com'
# Windows
cmdkey /delete:git:https://github.com
# gh CLI, all platforms
gh auth logout && rm -f ~/.config/gh/hosts.yml
Run polinrider and answer organization or account. It mirrors every
repository into an evidence folder before it changes anything, checks every
branch and tag, and offers the fixes above one repository at a time.
There are two ways the payload reaches a branch, and they need opposite fixes.
If the branch was force-pushed to a rewritten history, GitHub's push record
still holds the earlier commit and restore goes back to it. If the payload
was committed normally on top, there is no earlier state to return to, so
remove or erase is the fix. The tool works out which one it is looking at
and offers only what applies.
./ci/install-workflow.sh /path/to/your/repo
That vendors a scanner and the indicator set into .github/polinrider/ in your
repository, so the scan runs from code you control, with no marketplace action
fetched on every push. It commits nothing; review, then commit. The CI scanner
is shell, scans every ref of the history, and reads escape codes and package
names the way the binary does. If you vendored it from 2.1.0 or earlier, re-run
the installer with --force. See ci/README.md.
| Control | Why it matters for this specific attack |
|---|---|
| Require signed commits, org-wide | The single highest-value control. The propagation script amends and backdates commits; signing makes that immediately visible |
| Block force pushes and restrict deletions on every repo, no exceptions | One whitelisted "unimportant" repo is how an org gets in through the side door |
| Require PR review, dismiss stale approvals | Nothing reaches a default branch unseen |
CODEOWNERS on .vscode/, *.config.*, package.json, lockfiles, .github/workflows/ | The exact paths this malware writes to |
| Secret scanning and push protection, including a historical scan | Finds secrets the malware may have committed |
| Short-lived fine-grained PATs only; OIDC federation for cloud CI | Removes the long-lived tokens the stealer looks for |
// VS Code user settings.json
{
"task.allowAutomaticTasks": "off", // kills the folderOpen vector outright
"security.workspace.trust.enabled": true,
"security.workspace.trust.startupPrompt": "always",
"terminal.integrated.allowWorkspaceConfiguration": false,
"extensions.autoUpdate": false,
"extensions.autoCheckUpdates": false
}
npm config set ignore-scripts true # then allow per project where a build needs it
Pin exact dependency versions, commit lockfiles, use npm ci --ignore-scripts
in CI, and proxy your registry if you can.
Weekly for a month, then monthly. Reinfection with a rotated signature is documented behaviour, so a one-off scan is not enough.
The documentation site covers what the campaign is, how it hides and every indicator this tool carries, with a guide for each kind of cleanup.
Do not take a security tool's word for its own integrity. Check it.
# every commit on main is signed; GitHub shows "Verified" on each one
git log --show-signature -1
# a release archive matches its checksum and its provenance attestation
sha256sum -c SHA256SUMS --ignore-missing
gh attestation verify polinrider-vX.Y.Z-linux-x86_64.tar.gz --repo meSingh/polinrider-cleaner
| Signal | What it actually proves |
|---|---|
| OpenSSF Scorecard | 18 automated checks: branch protection, pinned dependencies, token permissions, dangerous workflow patterns, release signing |
| Signed commits | Every commit was made by the key holder. This is the direct counter to the campaign's backdated-amend technique |
| Build provenance on releases | Each binary came from this repository's CI at that tag, unmodified |
Protected main | No force pushes, no deletions, even by the owner |
| Pinned action SHAs | No workflow here can change under you when a third party moves a tag |
| Zero runtime dependencies | The crate depends on nothing, so nothing is fetched to build it or to run it |
| A conformance corpus | Every behaviour is pinned by a case in conformance/ that argues for its expected result |
Point your agent at AGENTS.md. It follows the AGENTS.md convention and tells an agent the fixed order of operations, which commands are read-only, which ones need your explicit confirmation, and how to read the output without drawing the wrong conclusion.
Issue and pull request templates are in .github/. False positives
and missed detections are the two most useful things you can report. See
CONTRIBUTING.md, HACKING.md and
CODE_OF_CONDUCT.md.
Security issues in this tool go through SECURITY.md, not a public issue.
This is an independent open source tool written by one person. It is not a product, and it is not affiliated with or endorsed by GitHub, Socket, OpenSSF, any security vendor, or any employer. The researchers cited in the indicator reviews are the public source of the indicators; that is a citation, not a partnership.
It is provided as is, with no warranty and no liability. You are responsible
for establishing that you are authorised to scan or modify whatever you point it
at, which matters most if that is an organization account rather than your own.
The GitHub fixes push to GitHub and --apply moves files on your machine; both
run with your credentials, at your instruction, and the dry run exists so you
can read the plan first.
Nothing here is legal or compliance advice.
Read DISCLAIMER.md before running this against anything you cannot afford to break.
This campaign rotates its signatures, so the indicator set is reviewed weekly against the published sources and every review is written down, including the weeks that found nothing. Each review keeps its own dated entry, and entries are never edited or removed: the indicator reviews.
Last reviewed: 12 October 2026.
Twelve indicators added from the OpenSSF malicious-packages database: nine
package names that install the campaign's MicrosoftSystem64 implant or are
named as PolinRider, one more at review tier, a C2 address and the implant's
Windows launcher. No new primary analysis was found, and most sources could
not be reached from the review environment this week.
The review also found that the folder and machine checks have never read
weak.txt; review-tier indicators reach only the CI scanner and the GitHub
checks. Read the entry before relying on a review line from a local scan.
Detect and clean up after the PolinRider supply-chain campaign: GitHub organizations, personal accounts and macOS, Linux and Windows machines. One binary, dry-run by default.
See the code
Detect and clean up after the PolinRider supply-chain campaign, on a developer machine, in a folder of code, on a GitHub organization or on a personal account.
One program. Nothing else to install. It changes nothing until you type yes.
[!CAUTION] Mid-incident? Download, verify, run. It asks what you need, one question at a time, and tells you what it found in plain words before it offers to change anything.
Download the archive for your machine from the latest release: macOS (Apple silicon or Intel), Linux (x86_64 or arm64) or Windows (x86_64).
Verify it before running it. This is a tool for recovering from tampered code, so take it at its word about nothing:
sha256sum -c SHA256SUMS --ignore-missing
gh attestation verify polinrider-v2.1.0-macos-arm64.tar.gz --repo meSingh/polinrider-cleaner
Unpack and run it, with no arguments:
tar -xzf polinrider-v2.1.0-macos-arm64.tar.gz
cd polinrider-v2.1.0-macos-arm64
./polinrider
On macOS the binary is not notarised, so Gatekeeper stops the first run.
Once you have verified it: xattr -d com.apple.quarantine ./polinrider.
Keep the ioc/ folder beside the binary. That is the indicator set, and the
binary refuses to scan without it.
Or through a package manager, which puts the binary and ioc/ in place:
brew install meSingh/tap/polinrider-cleaner # macOS and Linux
scoop bucket add mesingh https://github.com/meSingh/scoop-bucket
scoop install polinrider-cleaner # Windows
Since 2.1.0, releases also carry a .deb for Debian and Ubuntu, x86_64 and
arm64, attested like the archives. Verify it as above, then:
sudo apt install ./polinrider-vX.Y.Z-linux-x86_64.deb
A winget package is being prepared and is not in the winget repository yet.
Every release since 2.0, with what it changes and downloads for each system, is on the Releases page.
1.x, the shell tool, is frozen and still available from v1.0.9.
The first screen asks what to check. Answers are words, never numbers.
| Answer | What it checks |
|---|---|
computer | This machine: your whole home folder, plus login items, scheduled jobs, shell startup files, git and npm settings, running programs and open connections |
folder | One folder of code. It suggests the code folders it finds |
organization | Every repository, branch and tag of a GitHub organization you belong to |
account | The same for your own GitHub account |
everything | This computer first, then GitHub, one after the other |
It shows how far it has come while it works. Then a short summary in plain
words, with details for the full list, and one question about what to do.
It changes something only on a typed yes. q leaves. Input that ends
stops the session. Every run saves a full report in your home folder and names
it on the last screen.
On GitHub, for each infected repository it offers what can really fix it:
| Choice | What it does |
|---|---|
restore | Moves each branch back to the newest clean state on GitHub's own push record. Offered only where that record shows one. Never trusts commit dates, which this malware forges |
erase | Rewrites history so the payload is in no commit, then shows how to reset every existing clone |
remove | One new commit that takes the payload out. History is left as it is |
archive | Puts a large infected notice at the top of the README, rewrites the description and makes the repository read-only. Removes nothing |
skip | Leaves it |
Each one shows a dry run of exactly what would change on GitHub before it asks.
all applies one fix to every repository that can take it, behind a warning,
and goes ahead only when you type the organization's name.
GitHub checks need gh installed and signed in. It
checks that first and tells you what to run if not.
polinrider check ~/code # read-only scan of this machine and that folder
polinrider check --fs-only ~/code # only the folder, nothing about this machine
polinrider check --apply ~/code # move confirmed files into quarantine. Never deletes
polinrider clean ~/code/shop # what it would cut out of an infected build config
polinrider clean --apply ~/code/shop
polinrider --help
clean strips a payload appended to a build config in place and keeps the
infected original in quarantine. It never touches git: nothing is staged,
committed, reset or stashed.
| Exit code | Meaning |
|---|---|
0 | Clean against the current indicator set |
1 | Review items only |
2 | A confirmed indicator |
3 | The scan could not run. Nothing was checked |
A clean result means the current indicators were not found. It is not a certificate.
The tool covers steps 1 and 3. Step 2 is yours, and it is the one that matters most.
Run polinrider and answer computer on every machine that has touched the
affected repositories. The verdict is followed by what to do on that machine.
Sources disagree, so here is the rule this repository uses.
The tool says "rebuild" only when it has found proof that the payload ran on that machine. Either way, credential rotation is not optional.
Assume everything reachable from the affected user account is in someone else's hands. Do this from a machine you trust, before restoring any branch.
[!IMPORTANT] If a crypto wallet or seed phrase was on that machine, move the funds now. This malware targets them specifically.
First, the ones that grant repository write:
gh api /repos/OWNER/REPO/keysSecond, publish rights:
npm token list for tokens you did not createThird, everything else the stealer could read:
~/.aws/credentials, ~/.config/gcloud, service account keys.env on the affected machineCached git credentials survive a password change. Clear them:
# macOS
git credential-osxkeychain erase <<< $'protocol=https\nhost=github.com'
# Windows
cmdkey /delete:git:https://github.com
# gh CLI, all platforms
gh auth logout && rm -f ~/.config/gh/hosts.yml
Run polinrider and answer organization or account. It mirrors every
repository into an evidence folder before it changes anything, checks every
branch and tag, and offers the fixes above one repository at a time.
There are two ways the payload reaches a branch, and they need opposite fixes.
If the branch was force-pushed to a rewritten history, GitHub's push record
still holds the earlier commit and restore goes back to it. If the payload
was committed normally on top, there is no earlier state to return to, so
remove or erase is the fix. The tool works out which one it is looking at
and offers only what applies.
./ci/install-workflow.sh /path/to/your/repo
That vendors a scanner and the indicator set into .github/polinrider/ in your
repository, so the scan runs from code you control, with no marketplace action
fetched on every push. It commits nothing; review, then commit. The CI scanner
is shell, scans every ref of the history, and reads escape codes and package
names the way the binary does. If you vendored it from 2.1.0 or earlier, re-run
the installer with --force. See ci/README.md.
| Control | Why it matters for this specific attack |
|---|---|
| Require signed commits, org-wide | The single highest-value control. The propagation script amends and backdates commits; signing makes that immediately visible |
| Block force pushes and restrict deletions on every repo, no exceptions | One whitelisted "unimportant" repo is how an org gets in through the side door |
| Require PR review, dismiss stale approvals | Nothing reaches a default branch unseen |
CODEOWNERS on .vscode/, *.config.*, package.json, lockfiles, .github/workflows/ | The exact paths this malware writes to |
| Secret scanning and push protection, including a historical scan | Finds secrets the malware may have committed |
| Short-lived fine-grained PATs only; OIDC federation for cloud CI | Removes the long-lived tokens the stealer looks for |
// VS Code user settings.json
{
"task.allowAutomaticTasks": "off", // kills the folderOpen vector outright
"security.workspace.trust.enabled": true,
"security.workspace.trust.startupPrompt": "always",
"terminal.integrated.allowWorkspaceConfiguration": false,
"extensions.autoUpdate": false,
"extensions.autoCheckUpdates": false
}
npm config set ignore-scripts true # then allow per project where a build needs it
Pin exact dependency versions, commit lockfiles, use npm ci --ignore-scripts
in CI, and proxy your registry if you can.
Weekly for a month, then monthly. Reinfection with a rotated signature is documented behaviour, so a one-off scan is not enough.
The documentation site covers what the campaign is, how it hides and every indicator this tool carries, with a guide for each kind of cleanup.
Do not take a security tool's word for its own integrity. Check it.
# every commit on main is signed; GitHub shows "Verified" on each one
git log --show-signature -1
# a release archive matches its checksum and its provenance attestation
sha256sum -c SHA256SUMS --ignore-missing
gh attestation verify polinrider-vX.Y.Z-linux-x86_64.tar.gz --repo meSingh/polinrider-cleaner
| Signal | What it actually proves |
|---|---|
| OpenSSF Scorecard | 18 automated checks: branch protection, pinned dependencies, token permissions, dangerous workflow patterns, release signing |
| Signed commits | Every commit was made by the key holder. This is the direct counter to the campaign's backdated-amend technique |
| Build provenance on releases | Each binary came from this repository's CI at that tag, unmodified |
Protected main | No force pushes, no deletions, even by the owner |
| Pinned action SHAs | No workflow here can change under you when a third party moves a tag |
| Zero runtime dependencies | The crate depends on nothing, so nothing is fetched to build it or to run it |
| A conformance corpus | Every behaviour is pinned by a case in conformance/ that argues for its expected result |
Point your agent at AGENTS.md. It follows the AGENTS.md convention and tells an agent the fixed order of operations, which commands are read-only, which ones need your explicit confirmation, and how to read the output without drawing the wrong conclusion.
Issue and pull request templates are in .github/. False positives
and missed detections are the two most useful things you can report. See
CONTRIBUTING.md, HACKING.md and
CODE_OF_CONDUCT.md.
Security issues in this tool go through SECURITY.md, not a public issue.
This is an independent open source tool written by one person. It is not a product, and it is not affiliated with or endorsed by GitHub, Socket, OpenSSF, any security vendor, or any employer. The researchers cited in the indicator reviews are the public source of the indicators; that is a citation, not a partnership.
It is provided as is, with no warranty and no liability. You are responsible
for establishing that you are authorised to scan or modify whatever you point it
at, which matters most if that is an organization account rather than your own.
The GitHub fixes push to GitHub and --apply moves files on your machine; both
run with your credentials, at your instruction, and the dry run exists so you
can read the plan first.
Nothing here is legal or compliance advice.
Read DISCLAIMER.md before running this against anything you cannot afford to break.
This campaign rotates its signatures, so the indicator set is reviewed weekly against the published sources and every review is written down, including the weeks that found nothing. Each review keeps its own dated entry, and entries are never edited or removed: the indicator reviews.
Last reviewed: 12 October 2026.
Twelve indicators added from the OpenSSF malicious-packages database: nine
package names that install the campaign's MicrosoftSystem64 implant or are
named as PolinRider, one more at review tier, a C2 address and the implant's
Windows launcher. No new primary analysis was found, and most sources could
not be reached from the review environment this week.
The review also found that the folder and machine checks have never read
weak.txt; review-tier indicators reach only the CI scanner and the GitHub
checks. Read the entry before relying on a review line from a local scan.