PushWarden docs

What it detects

Looking for the families PushWarden covers rather than the rules? See What it covers.

PushWarden looks for the artefacts and behaviour of the PolinRider / Contagious Interview campaign in two places: your repositories and the computer itself. It also has generic rules for the tricks this kind of malware uses, so a new campaign with different strings is still caught. Every finding has a severity, a category, a threat name, the evidence and a one-sentence reason.

Severities

Severity Meaning Automatic action
CRITICAL A confirmed malware artefact or activity: a PolinRider indicator, or code that runs by itself and downloads or hides what it runs Yes, according to the action policy
HIGH A strong indicator that needs a human decision, for example a compromised package version or exposed keys on an infected host No, listed for review
WARNING Context worth checking, for example a suspicious git reflog, a secrets file in an infected repository, or files that arrived in the same commit as a loader No
INFO Informational No

Scan commands exit with code 1 when anything HIGH or CRITICAL is found.

In repositories

Every script, config, font, image and .vscode/*.json file in a project is checked, not only files with well-known names. node_modules and vendor are skipped unless --deep or deep=true is set.

What Examples Severity
Campaign signatures and build tags in any script file Loader marker strings, obfuscator fingerprints and build tags found in .js, .ts, .json, .py, .sh, .bat, .ps1 and similar files CRITICAL
Payload injected into a config file Code appended, prepended or inserted into postcss.config.mjs, eslint.config.mjs, next.config.js, tailwind.config.js and other framework configs CRITICAL
Entry-file hooks A loader call added to an application entry file CRITICAL
Known keys and wallets The campaign’s XOR payload keys; TRON, Aptos and Ethereum wallet addresses used as dead drops; blockchain RPC hosts next to a marker CRITICAL
C2 references Known C2 IP addresses, first-stage hosts, C2 URL paths and the payload header CRITICAL
Disguised payloads A font, image or dictionary file whose bytes are code, with or without whitespace padding; files matching known loader hashes CRITICAL
Editor autorun .vscode/tasks.json with runOn: folderOpen together with a loader or C2 host; settings that force automatic tasks on CRITICAL or HIGH
Propagation scripts The batch scripts the malware uses to commit and force-push itself, and the helper files they create CRITICAL or HIGH
.gitignore tampering Entries that hide the malware’s files, or the .gitignore hiding itself CRITICAL or HIGH
Git hooks and git config Malicious hooks; settings in .git/config that make git run a command (core.fsmonitor, core.pager, core.sshCommand, credential.helper, filter.*, aliases that start with !) when the command downloads or runs code. Ordinary values such as less, code --wait or git-lfs are left alone CRITICAL
Compromised packages Known bad versions of npm packages, Go modules and a Packagist package, including typosquats; suspicious lifecycle scripts CRITICAL or HIGH
Git history Backdated commits, force-push traces in the reflog, commits that touch payload code on any branch. With scan --history, every old file version that carries an indicator HIGH or WARNING
Camouflage Files that arrived in the same commit as a loader: decoy fonts, a decoy README, a .vscode set WARNING

Beyond PolinRider

Campaigns change their strings; their tricks stay the same. These rules look for the trick itself, whatever the campaign. They act on their own only when the code runs by itself and the evidence is strong; everything else is an alert for you to review. On the same file, a PolinRider finding takes precedence and keeps its PolinRider name.

What Examples Severity Automatic action
A task that downloads and runs code when a folder is opened .vscode/tasks.json with runOn: folderOpen running curl … \| sh, iwr … \| iex, powershell -enc … CRITICAL Quarantined
A git hook that downloads and runs code .husky/*, .git/hooks/* or a core.hooksPath folder fetching and running a script CRITICAL Quarantined
Code hidden off-screen 150 or more spaces or tabs on a line, then code that can run code (eval, Function(, child_process, require(, exec(, atob() CRITICAL Pro removes only the hidden code; the free version quarantines the file
An install script that downloads and runs code preinstall, install, postinstall or prepare in package.json running curl … \| sh and similar CRITICAL None: package.json is never moved. Install with --ignore-scripts until it is reviewed
An obfuscated loader A config file, entry file or the script an install hook runs that looks machine-obfuscated (_0x names, while(!![]), packed strings, escape runs) and can run code. Minified bundles are not judged HIGH None
An install or container step that downloads and runs code setup.py that executes downloaded or decoded code; .devcontainer commands, including initializeCommand, which runs on your computer HIGH None
Invisible characters (GlassWorm style) A dozen or more zero-width or hidden characters in a row in a code file. Critical when the same file also decodes text and runs code; otherwise a warning. Emoji joiners are normal and ignored CRITICAL or WARNING The file is quarantined
The Shai-Hulud npm worm setup_bun.js, bun_environment.js, .truffler-cache and its planted workflow; an install script that runs them CRITICAL or HIGH None for the files; install with --ignore-scripts
An AI coding tool run with its safety checks off An install script that starts claude, gemini, codex and similar with --dangerously-skip-permissions, --yolo or --full-auto, the way the s1ngularity attack hunted for secrets CRITICAL None: package.json is never moved
Fake job-interview spyware A keylogger and a screen grabber (node-global-key-listener with screenshot-desktop) in one project HIGH None
AI assistant config .claude/settings.json hooks, MCP files and rules files (CLAUDE.md, AGENTS.md) that download and run code, start a script hidden in an editor folder, or hide text in invisible characters CRITICAL, HIGH or WARNING None
GitHub Actions workflows A step that sends every secret to a network command, installs a self-hosted runner, or runs a pull request’s own code with pull_request_target HIGH or WARNING None

“Downloads and runs” means a command that fetches code and executes it in one go, such as piping curl or wget into a shell, Invoke-WebRequest into Invoke-Expression, an encoded PowerShell command, node -e or python -c with decoding or network code, or certutil, mshta and bitsadmin downloads. Downloading a file without running it is not flagged. The patterns are in the indicator file (download_exec_regexes, exec_sink_regexes, hidden_padding_min), so they are tuned through the daily indicator update. The newer family rules (worm_files, ai_cli_abuse_regexes, suspicious_dep_sets) work the same way. A file too large to read whole still has its last megabyte checked, because a payload is appended to the end.

On the host

Host checks run in scan (unless --no-system) and in the guard’s full sweep. Process and network checks also run every 5 seconds.

What Examples Severity
Malicious processes A running process whose command line carries a campaign marker, or a known payload process name CRITICAL
C2 connections An open socket to a known C2 address CRITICAL
Remote access tool footprint The directories, files and environment variables the campaign’s remote access tools leave behind; portable Node or Python runtimes dropped by a later stage CRITICAL or HIGH
Persistence Entries in cron, systemd user units, XDG autostart, LaunchAgents, Windows scheduled tasks, the Startup folder and the HKCU\...\Run registry key CRITICAL or HIGH
Shell start-up injection Lines added to shell rc files or the PowerShell profile HIGH
Editor and tool injection Modified files inside VS Code, Cursor, VSCodium, Windsurf, Discord or GitHub Desktop; a backdoored or oversized global npm/lib/cli.js CRITICAL or HIGH
Hosts-file redirects Package registries redirected in the hosts file HIGH
Exposed credentials on an infected host Plain-text tokens in .npmrc, .git-credentials, the gh, AWS and Docker configs, and SSH keys. Reported only when the host shows other signs of infection HIGH

Threat names

Notifications, dialogs, reports and the history name threats in the style of Windows Defender, so an alert is recognisable at a glance.

Threat name What it is
Trojan:JS/PolinRider.ConfigInject Loader code injected into a framework config file, or a known XOR key
Trojan:JS/PolinRider.EntryHook Loader call added to an application entry file
Trojan:JS/PolinRider.FakeFont A script disguised as a font file
Trojan:JS/PolinRider.Disguised Code hidden in another non-code file type
Trojan:Script/PolinRider.TaskJacker An editor task that runs the loader when a folder is opened
Trojan:BAT/PolinRider.AutoPush The propagation script that commits and force-pushes the backdoor
Trojan:JS/PolinRider.DeadDrop A blockchain wallet or RPC reference used to fetch the payload location
Trojan:JS/PolinRider.C2 A reference to a command-and-control server
Trojan:JS/OmniStealer.Exfil Credential exfiltration through a messaging API
Trojan:Script/PolinRider.GitHook A malicious git hook
Trojan:JS/PolinRider.Package A compromised package version
Trojan:JS/PolinRider.EditorInject Code injected into an editor or desktop tool
Trojan:Script/PolinRider.Hide .gitignore entries that hide the malware
Behavior:Node/PolinRider.Payload A running payload process
Behavior:Net/PolinRider.C2 A live connection to a C2 server
Backdoor:JS/RuntimeDevLink The remote access tool and its runtime
Backdoor:Script/PolinRider.Persist A persistence entry
Trojan:Script/PolinRider Any other injected loader
Trojan:Script/Generic.AutoRun A task that runs by itself and downloads code (editor task, dev container)
Trojan:Script/Generic.Downloader A git hook or install script that downloads and runs code
Trojan:JS/Generic.HiddenCode Code hidden off-screen behind whitespace
Trojan:JS/Generic.Obfuscated An obfuscated loader in a hand-written file
Trojan:JS/GlassWorm.Invisible Code hidden in invisible characters
Worm:JS/ShaiHulud A file or install script used by the Shai-Hulud npm worm
Trojan:Script/Generic.AIAbuse An install script that starts an AI coding tool with its safety checks off
Trojan:JS/ContagiousInterview.Spy The keylogger and screen grabber used by fake job-interview projects
Trojan:Script/Generic.AgentHook An AI assistant config or rules file that runs or hides a command
Trojan:Script/Generic.CIWorkflow A workflow that sends secrets out or installs a runner

Windows Defender itself names the family Trojan:JS/PolinRider.DB!MTB.

Reasons on every finding

Every finding has a why line: one sentence that says what the evidence means and why it has that severity. Every response has a response line: what was done and why (killed, stripped, quarantined, or left for you to decide).

These lines appear in scan output, dialogs, pushwarden alerts, pushwarden history, desktop notifications, the webhook JSON (reasons array) and guard.log.

Indicators are data

All indicators live in one file, iocs.json: signature strings and regular expressions, XOR keys, wallet addresses, C2 IPs and hostnames, URL paths, loader hashes, process markers and compromised package versions.

  • The program carries an embedded copy, so it works offline from the first run.
  • The guard downloads a newer copy from the project repository every 24 hours. The download is parsed and validated before it replaces the previous copy: every regular expression must compile, every IP and hash must be well formed, and the version must be newer.
  • pushwarden update-iocs forces a download. --url and the ioc_update_url setting point it at a mirror or a fork.
  • New variants are usually covered by adding an indicator, with no new release.

The current list is in pushwarden/iocs.json in the repository.

Kill list versus report list

A process is killed only when its command line holds a strict marker that legitimate tools never use. A broad indicator alone is reported but not killed, and the reason says so. This is what keeps ordinary node processes safe.

Files that legitimately contain signatures

Rule sets, tests and indicator databases contain the same strings the scanner looks for. Such a file opts out by carrying the token pushwarden:allow-signatures in its first 512 bytes.

The token is never honoured for config files, entry files, asset files or JavaScript and TypeScript files, so malware cannot use it to hide.

False positives

If a legitimate file is flagged:

pushwarden history --allow PATH --note "what the file really is"

This restores the file, stops flagging that exact content for 30 days, and records the note as feedback. Use exclude in the configuration to skip a directory entirely. See Response and recovery.