Every command, and what it will not do
snare is about 2,200 lines of bash. No framework, no
npm install — deliberately, because npm is the attack surface. The core heuristic
is language-agnostic; the config list is tuned for the JavaScript ecosystem, because that is
where this family lives.
The guided one
If you do not want to choose between the commands below, this runs the whole sequence and asks you at each step. It is the same work, in the order that actually helps.
snare respond walk through everything, one question at a time snare respond --status where you got to snare respond --reset start the walkthrough again
Six steps: check the machine, rotate credentials, clean the machine, find what is infected, fix it, tell your collaborators — then a verification pass that re-reads rather than trusting what just happened. Anything that writes to a repository defaults to no, and the choice to purge history is asked once rather than per repository. Progress is saved, so stopping and resuming is fine.
It refuses to run unattended. With no terminal, or CI set, it
exits rather than prompting where nobody can answer — use the individual commands there.
Scanning
snare scan scan the current directory snare scan repo [path] one clone: working tree, all branches, full history snare scan repo [path] --new only findings that are not in the baseline snare scan orgs list every org/account you can reach, with repo counts snare scan orgs --all include individual accounts, not just organisations snare scan github every repo you can reach, via the API (no cloning) snare scan github --pick choose which orgs to scan, from a list snare scan github --owner a,b only these orgs/accounts (comma-separated) snare scan github --mine only repos you own snare scan github --all-branches snare doctor this machine, persistence spots, and your GitHub account snare selftest prove detection still works against known samples
Scanning everything is often the wrong first move.
A real account reaches further than people expect — one we tested sees 22
organisations and roughly 950 repositories, which is several thousand API calls.
scan orgs lists what is reachable and how big each owner is, so you can
start with the ones that matter; scan github --pick shows the same list
and lets you choose from it. The repo counts are the number
--owner will actually scan, not an estimate.
Read this before trusting a clean result.
scan github reads branch tips through the API. It cannot see
history, and it fetches a bounded set of files. A payload that was committed and
later removed, or one hidden somewhere it does not look, will not appear.
scan repo against a real clone is the thorough check — it walks every
branch and every commit. The infection that started all of this survived a clean
127-repository API sweep and was only caught by scan repo.
selftest exists because four defects once made snare
report clean on infected repositories. It builds a scratch repo of known-bad
and known-good samples, runs the real scanner, and exits non-zero on any mismatch.
If it fails, do not trust any clean result until it passes.
The gate, for you and for coding agents
snare agents install gate every clone, pull, install and run an agent does
snare agents status | uninstall
snare agents install --project also this repo's CLAUDE.md / AGENTS.md and a project hook
snare gate -- <command> scan first; exit 0 means safe to run it
snare gate allow [dir [branch]|url] you looked and it is yours: allow what was flagged,
on disk or still incoming (a branch, a pull, a clone)
snare gate allowed | forget review or withdraw what you allowedA block is what snare suspects, not a verdict. It lists each
finding as the file, the line and what is suspected, so you can look — it can be a false
alarm. Then you decide: snare gate allow if it is yours, or
snare respond to let snare take charge. Approvals live in
~/.snare, never in the repository, and lapse if the file changes. A coding agent
cannot approve its own block: it is told to stop and show you, and an agent that tries is
refused.
The gate answers one question before a command runs: does it
bring code in or execute it, and is that code clean? git clone is cloned to a
quarantine directory and scanned first. git pull, merge,
rebase and checkout fetch — which runs nothing — and scan the incoming
tree in a throwaway worktree before a file lands. Installs and npm run scan the
directory they would run in. It fails closed: if the scan cannot finish, the command does not
run.
For coding agents, snare agents install adds a
Claude Code PreToolUse hook — enforced, so the agent cannot skip it — and a short
instruction block in CLAUDE.md, AGENTS.md and GEMINI.md
for agents without hooks. A block reads "FIRE ON THE MOUNTAIN", tells the agent to stop
and tell you, and raises a desktop notification. Ordinary commands like git status
cost about 30ms. install.sh and snare update show what it would change
and ask first; after a yes, snare update keeps it current, and
snare agents uninstall removes it and keeps it removed.
Known-malicious packages: npm, Python, Rust, Go and more
snare deps [dir] lockfiles, node_modules and virtualenvs against the list snare deps --system also your global Python, pipx, npm and cargo installs snare deps fix [dir] remove each one, or replace it with a version not listed snare deps update | status refresh / show the known-malware list
The list is OpenSSF's
malicious-packages database — about 117,000 packages across npm, PyPI, crates.io, Go,
RubyGems, Packagist, NuGet and Maven — condensed every six hours into one small file.
Every snare downloads the same file and matches it on its own machine, so
nothing about your packages is ever sent. Lockfiles read: npm, pnpm, yarn, bun,
requirements*.txt, poetry, uv, pdm, pylock.toml, Pipfile,
Cargo.lock, go.sum, Gemfile.lock,
composer.lock and NuGet's packages.lock.json.
The gate uses the same list: npm i x@1.2.3,
npx, pip install x==1.0, uv add, cargo add,
go get, gem install and composer require are stopped
when the version is listed, and a repository's lockfiles are checked before
npm install or pip install -r. It also treats the files other
languages run on their own as routes: setup.py, conftest.py,
build.rs, sitecustomize.py, .pth files (Python runs them at
every start — the LiteLLM 1.107.0 payload was one) and //go:generate lines.
Guard and shield
snare guard install kill the loader on sight, from login onwards snare guard scan one-shot check of this machine snare guard status | log | stop | start | uninstall snare shield install scan before npm/pnpm/yarn/bun/npx and git clone run snare shield status | uninstall snare shield check [path] the fast check on its own (~22ms)
The guard is a background process (launchd on macOS, a systemd user unit on Linux, a logon Scheduled Task on Windows). It polls once per second, so a payload can still act before it is caught, and it cannot see code already running inside another process.
The shield wraps package managers in your shell so a
scan happens before they can execute anything. Honest limits: it is shell-only, so it
cannot see VS Code's own folderOpen task — that is the guard's job — and
anyone can bypass it with command npm or SNARE_SKIP_SHIELD=1.
It is a seatbelt, not a sandbox.
Hook and CI
snare hook install block a push that carries a payload snare hook install --with-pre-commit snare hook status | uninstall snare ci install write a GitHub Actions workflow into this repo snare ci status is the workflow installed, and is the check required? snare ci print print the workflow without writing it
This family injects into commits from an already-infected machine, so the payload leaves before anyone scans a repository. The hook is the only feature that prevents that rather than detecting it afterwards. It also refuses the push when the machine itself has a live detection, because a clean repo pushed from an infected machine can still carry the payload out.
GitHub.com cannot reject a push outright — server-side
pre-receive hooks are an Enterprise feature. A required status check is the practical
equivalent: the merge button stays disabled until the scan passes. ci install
writes the workflow, but you must tick it as required under
Settings → Branches. Until you do, it reports and blocks nothing —
snare ci status will tell you when that is the case.
Fix, rotate and notify
snare fix <owner/repo> dry run (always backs up first) snare fix <owner/repo> --push clean branch tips and push snare fix <owner/repo> --purge-history --push erase it from all history snare fix --all every repo the last scan flagged snare fix --pick choose orgs, scan them, then fix snare fix --pick --purge-history --push ... and erase it from all history snare fix --owner a,b --push the same, without the prompt snare rotate what to revoke, in order, plus a GitHub audit snare notify <owner/repo> list collaborators and preview the notice snare notify <owner/repo> --issue | --mail
Rotate before you clean. Stealing credentials is what
this family is for; the file in your repository is delivery, not payload.
Removing it does not un-steal a token. snare rotate lists what to revoke
in order — npm write tokens first, because a stolen one lets the worm publish
trojanised versions of your other packages under your name — and audits your GitHub
account for the repositories and secret-exporting workflows this family leaves behind.
The full sequence is on the response page.
fix --pick is the whole clean-up in one pass:
choose organisations from the same list scan orgs prints, scan them, then
remediate what was found. It is a dry run unless you add --push, and the
destructive flags stay on the command line rather than hiding inside a friendlier verb —
force-pushing rewritten history across an organisation should be legible in your shell
history. --owner a,b is the same thing without the prompt, for a script or a
timer. With --purge-history it checks for git-filter-repo
before scanning, rather than after asking you to choose and then failing on the
first repository.
--purge-history rewrites every commit, so every SHA changes.
Collaborators must re-clone, not pull, and forks and pull-request refs still
hold the old objects. fix --all is a dry run by default and requires you to
type the repository count to confirm, because it can force-push.
Reports, baseline and update
snare report scan, save a timestamped report snare report --json machine-readable; exits non-zero on findings snare schedule install --daily run it on a timer and notify on findings snare schedule status | uninstall snare baseline set accept the current findings as known snare baseline status | clear snare update pull the latest version snare update --check is a newer version available? snare update --relink point ~/.local/bin/snare at this checkout snare version version and commit
Reports write to $SNARE_HOME/reports and exit non-zero when
something is found, so a timer or a CI job can act on the result. There is deliberately
no email: SMTP credentials in a security tool are a liability, and a
report file plus a native notification does the same job with nothing worth stealing.
Accepting a finding into a baseline silences it — it does not make it safe. Only baseline what you have actually read.
snare update matters more than it looks. Every clone made
before 27 August 2026 contains defects that made the working-tree scan report
clean on infected repositories. If you installed snare before then, update
before you trust it, and run snare selftest afterwards. The
changelog lists every one of them.
What snare does not do
A security tool that oversells itself is worse than no tool at all.
The guard polls once per second, so a payload can still act before it is caught. It cannot see code already running inside another process, and it will miss variants built on different infrastructure. Removing the malware from the repository is the actual fix — a running guard is never permission to open a repo you do not trust.
Scanning through the GitHub API sees branch tips only. A payload that was
committed and later deleted survives in history; use snare scan repo on a clone to
catch that.
Rewriting history does not touch forks, pull-request refs, or objects still reachable by SHA. Ask GitHub Support to garbage-collect after a purge.