1.**Clones** the default branch (shallow, `--depth=1`, git hooks disabled)
2.**Scans in-process** — reads files as inert text/bytes and pattern-matches known PolinRider signatures. It **never executes** the files it scans (see [Runtime hardening](#runtime-hardening)). Detection is _content-confirmed_: a repo is marked infected only when a real signature matches.
3.**Surgically remediates** infected repos — removing only what is confirmed malicious and preserving legitimate code, tasks, fonts, and configs:
- **Removes the entire `.vscode` directory** when any task/launch entry is malicious — `curl … | bash`, `runOn: folderOpen` auto-runs, C2 hosts, or running an interpreter against a font/asset (e.g. `node ./public/fonts/x.woff2`)
- **Removes font carriers strategically.** A font is a _confirmed carrier_ only when it is unreferenced, fails structural font validation (no valid magic / table directory — real fonts, including commercial `.otf` files with embedded license URLs and binary blobs, are trusted), _and_ contains an appended code payload. When a carrier is found, the scanner removes the carrier plus the whole Font-Awesome-named disguise set (`fa-brands/solid/regular-…`) and its `README.md`, while **preserving clean, non-`fa-` fonts in the same directory** (only the leaf `fonts/` dir is removed if nothing clean remains). `fa-`-named fonts with **no** payload are flagged `suspicious` for manual review — never auto-removed.
| `check`_(default)_ | Scan only. Exits non-zero when malware is found so a **required status check** blocks the merge. No token needed. | `pull_request` |
| `fix` | Surgically cleans the working tree. With `commit: true` it also commits + pushes the fix back to the branch (push events only). | `push` |
| `pr` | Cleans on a new branch and opens a cleanup PR (optionally auto-merged). Works where commit-back can't push (fork PRs, protected branches). | `schedule` / `workflow_dispatch` |
Ready-to-copy workflows live in [`examples/`](examples/).
### Enforce it (block infected merges)
`check` exits `1` on a confirmed infection. To turn that into a merge gate, add the
job as a **required status check**: repo → Settings → Branches (or Rulesets) →
require the `scan` job to pass. PRs then can't merge while malware is detected.
| `GH_ORG` | required\* | Organization name. Scans every repo in the org. |
| `GH_USER` | required\* | Personal username. Scans every repo you own (`affiliation=owner`). |
| `GH_REPO` | _(all repos)_ | Scope to specific repo(s), comma-separated. Each is `name` (account owner prefixed) or `owner/name`. Skips the account-wide listing — use it to pilot one repo first. |
| `DRY_RUN` | `false` | Skip push and PR |
| `AUTO_MERGE` | `false` | Auto-merge each cleanup PR after opening it. Respects branch protection (blocked PRs stay open and are reported). |
| `MERGE_METHOD` | `squash` | Merge method when `AUTO_MERGE=true`: `squash`, `merge`, or `rebase`. |
| `REPORTS_DIR` | `reports` | Where JSON + Markdown run reports are written. Set empty to disable. |
| `WORKSPACE` | `/workspace` | Where repos are cloned |
| `BRANCH_PREFIX` | `fix/polinrider-cleanup` | PR branch name prefix |
\* Set **exactly one** of `GH_ORG` or `GH_USER` — not both, not neither.
### Auto-merge
With `AUTO_MERGE=true`, after each PR is opened the tool runs `gh pr merge --<method> --delete-branch`. It **respects branch protection** — a repo that requires reviews or passing checks is left open and listed in the summary (and report) as "auto-merge blocked"; nothing is bypassed. The token must have merge rights on the repo.
### Reports
Every run writes `polinrider-<timestamp>.json` + `.md` (and `latest.json` / `latest.md`) to `REPORTS_DIR` (the mounted `./reports` volume in Docker). The files are rewritten after each repo, so you get a durable, auditable trace — including each repo's findings, what was removed/cleaned, the PR link, and merge status — instead of relying on scrollback. The Markdown includes a clickable **Repository | Pull request | Status** table.
---
## PR branch protection
Because your org has branch protection enforced, the tool:
- Creates a **new branch** per repo (never pushes to `main` or `master` directly)
- Opens a PR for review — a human merges it
- PR body explains exactly what was changed and why
---
## After merging PRs
1. Notify affected developers to check their machines for the initial dropper
2. Rotate any secrets that may have been exposed via committed `.env` files
3. Check npm global packages: `npm list -g --depth=0`
4. Check VS Code extensions for anything unfamiliar
5. Revoke and regenerate any PATs for accounts whose machines were compromised