Your dependencies are tainted. So is everyone else's logo.
taintwatch scans your local codebases for known-compromised package versions — the ones from Shai-Hulud, chalk/debug, Nx s1ngularity, Ultralytics, and the next one that lands tomorrow morning while you're asleep. It pulls real-time feeds from OSV.dev (MAL-* records), the OpenSSF malicious-packages repository, and Aikido Intel; matches every lockfile and every installed node_modules / site-packages directory under the roots you configure; and yells at you on Discord or via a desktop toast when one of your repos has malware on disk.
It does not run anything in the background until you ask it to. Nothing is auto-installed at install time. Same consent model as pre-commit install.
macOS / Linux
curl -sSL https://raw.githubusercontent.com/braasdas/taintwatch/master/install.sh | shWindows (PowerShell)
irm https://raw.githubusercontent.com/braasdas/taintwatch/master/install.ps1 | iexAny platform with Python 3.10+
pipx install taintwatch # recommended
# or: pip install --user taintwatchThen on first run:
taintwatch config init # write a starter config
taintwatch # launches the interactive TUIThe bare taintwatch command opens a full-screen interactive UI with menus for scanning, feed status, hit triage, and report browsing. Arrow keys to navigate, single-letter shortcuts (s scan, u feeds, h hits, f feeds status, r reports, c config, q quit). Press enter on a hit to see the full advisory + a copy-pastable remediation command.
If you prefer one-shot CLI use (cron, scripts):
taintwatch scan # one-shot scan, prints results, exits
taintwatch feeds update # refresh advisory feeds
taintwatch watch # foreground daemon loop
taintwatch ui # explicit alias for the TUIWhen taintwatch finds something, the desktop toast plays a different sound depending on how scared you should be:
| Severity | Trigger | Windows | macOS | Linux |
|---|---|---|---|---|
CRITICAL |
Compromised code is installed on disk in node_modules/ or site-packages/ — it can execute on your next build, dev server, or import. |
Looping alarm | Sosumi | dialog-warning.oga + urgency=critical |
HIGH |
Compromised version pinned in a lockfile. Next npm ci / pip install -r will pull it. |
Default ding | Glass | message-new-instant.oga + urgency=normal |
INFO |
Informational (e.g. a previously-flagged repo is now clean). | Short reminder | Tink | complete.oga + urgency=low |
The Discord webhook also escalates: @here ping only on CRITICAL; silent posts otherwise.
- Pulls from three free, no-auth, redistributable feeds: OSV.dev bulk mirror, OpenSSF
malicious-packages, Aikido Intel. - Parses lockfiles for npm, yarn, pnpm, pip (requirements + Pipfile), poetry, Cargo, Go modules.
- Walks
node_modules/andsite-packages/to find malware that's installed but no longer in your lockfile (the Shai-Hulud case where you rolled back the lockfile but didn'trm -rf node_modules). - Stores everything in one local SQLite file. No cloud, no telemetry, no account.
- Diffs scans so it alerts only on new hits.
- Optional opt-in autostart via Windows Task Scheduler, macOS launchd, or Linux systemd-user — one explicit command (
taintwatch install-autostart), one explicit uninstall.
taintwatch scan [--root PATH] [--deep/--no-deep] [--skip-update]
taintwatch watch # foreground daemon loop
taintwatch feeds update | status
taintwatch report show [--last N]
taintwatch config init | path
taintwatch install-autostart [--interval 60] [--dry-run]
taintwatch uninstall-autostart
taintwatch autostart-status
In 2025, ReversingLabs logged a 73% YoY jump in malicious open-source packages. Sonatype blocked 454,600 new ones. The Shai-Hulud worm self-replicated across ~500 npm packages in 72 hours (twice — September and November). The chalk/debug compromise sat live in ~2 billion weekly-download packages for two and a half hours. The Nx attack weaponized your AI CLIs against you. The xz backdoor took ~2.5 years of social engineering and was caught by accident because somebody noticed sshd was 500ms slower.
You cannot keep up by reading Twitter.
MIT for the code. Feed data has its own per-source licensing — OSV records are CC-BY 4.0 / Apache-2.0 (per record), OpenSSF data is Apache-2.0, and Aikido Intel's data license is not posted, so we use it only for local lookup. If your org needs redistributable-only sources, set [feeds] aikido = false in your config.
PyPI publish uses Trusted Publishing — no API tokens. Tag and push:
git tag v0.1.1
git push origin v0.1.1The .github/workflows/release.yml workflow builds the sdist + wheel with hatch build and publishes via OIDC.
