--- name: detecting-malicious-npm-packages description: Triage npm packages for install-script malware, exfiltration, and worming behavior. domain: cybersecurity subdomain: supply-chain-security tags: - supply-chain-security - npm - malware-analysis - guarddog - install-scripts - exfiltration - static-analysis - threat-detection version: '1.0' author: mahipal license: Apache-2.0 nist_csf: - DE.CM-09 mitre_attack: - T1195.002 --- # Detecting Malicious npm Packages > **Legal Notice:** Analyze packages in an isolated, disposable environment. Some malicious packages execute on install (`npm install` runs lifecycle scripts automatically) or on import. Never analyze a suspect package on a workstation with credentials, SSH keys, cloud tokens, or network access to production. This skill is for defensive analysis and authorized incident response only. ## Overview The npm registry is the largest software package ecosystem in the world and the most heavily targeted by supply-chain attackers. Malicious packages reach victims through typosquatting (`expresss`, `crossenv`), dependency confusion, account/maintainer takeover (the 2025 Shai-Hulud worm and the `event-stream` compromise are canonical examples), and starjacking. The defining danger of npm is that `npm install` automatically runs `preinstall`, `install`, and `postinstall` lifecycle scripts with the developer's full privileges **before any application code is invoked** — so simply installing a package is enough to be compromised. Roughly 2% of npm packages use install scripts, which makes them both common and a powerful malware delivery vehicle. Typical malicious behaviors are: exfiltrating environment variables, `~/.npmrc` tokens, SSH keys, and cloud credentials to an attacker-controlled URL; opening reverse shells; dropping cryptominers; reading and posting `process.env`; obfuscating payloads with base64/eval; and self-propagating (worming) by stealing the maintainer's npm token and republishing trojanized versions of other packages they own. This skill provides a repeatable triage workflow centered on **GuardDog** (Datadog's open-source heuristic scanner built on Semgrep + metadata rules), supplemented by manual tarball inspection, lockfile-based compromise checks against known-bad version lists, and dynamic detonation with network and filesystem monitoring. The goal is to decide, quickly and safely, whether a given package or a project's dependency tree contains malicious code. ## When to Use - Triaging a specific npm package before adding it as a dependency. - Vetting a full `package.json` / `package-lock.json` during code review or onboarding a third-party library. - Responding to a supply-chain advisory (e.g., a worm campaign) and needing to check whether your lockfiles pulled a known-bad version. - Investigating an endpoint or CI runner suspected of having installed a trojanized package. - Building a pre-install gate in CI/CD that blocks packages exhibiting malicious indicators. ## Prerequisites - An isolated VM or disposable container with **no production credentials** and snapshot/rollback capability. - GuardDog: ```bash pip install guarddog # or run via Docker without local install: docker pull ghcr.io/datadog/guarddog alias guarddog='docker run --rm ghcr.io/datadog/guarddog' ``` - Node.js + npm (use `--ignore-scripts` when downloading for analysis). - `jq`, `tar`, and optionally OSV-Scanner for known-vulnerability/known-malicious cross-checks: ```bash go install github.com/google/osv-scanner/cmd/osv-scanner@v1 ``` - For dynamic analysis: a sandbox with egress logging (e.g., `tcpdump`, a DNS sink, or a network namespace). ## Objectives - Statically scan an npm package (or a whole dependency tree) for malicious heuristics without executing it. - Identify install-script abuse, environment/credential exfiltration, obfuscation, and silent process execution. - Cross-check lockfile-pinned versions against known-malicious version lists / OSV. - Safely detonate a suspect package and observe network and filesystem behavior. - Extract indicators of compromise (URLs, IPs, hashes) for blocking and threat intel. - Produce a defensible verdict (benign / suspicious / malicious) with evidence. ## MITRE ATT&CK Mapping | Technique ID | Technique Name | Relevance | |--------------|----------------|-----------| | T1195.002 | Supply Chain Compromise: Compromise Software Supply Chain | Core technique — trojanized npm package delivered through the registry. | | T1059.007 | Command and Scripting Interpreter: JavaScript | Malicious install scripts / module code execute attacker JavaScript. | | T1552.001 | Unsecured Credentials: Credentials In Files | Packages steal `~/.npmrc`, `.env`, SSH keys, and cloud credential files. | | T1041 | Exfiltration Over C2 Channel | Stolen secrets posted to attacker HTTP(S) endpoints. | | T1027 | Obfuscated Files or Information | base64/eval/hex obfuscation hides the payload from review. | ## Workflow ### 1. Download the package without executing it Fetch the tarball with scripts disabled so nothing runs during acquisition. ```bash mkdir triage && cd triage # Resolve the tarball URL and download it (no install, no scripts) npm pack express@4.18.2 # produces express-4.18.2.tgz # or for an arbitrary version: npm view some-pkg@1.2.3 dist.tarball curl -sL "$(npm view some-pkg@1.2.3 dist.tarball)" -o some-pkg.tgz tar -xzf some-pkg.tgz # extracts into ./package ``` ### 2. Scan a single package with GuardDog GuardDog applies metadata + source heuristics and prints which rules matched. ```bash # Scan the latest published version from the registry guarddog npm scan express # Scan a specific version guarddog npm scan some-pkg --version 1.2.3 # Scan the local tarball / extracted directory you downloaded above guarddog npm scan ./some-pkg.tgz guarddog npm scan ./package/ ``` ### 3. Verify an entire dependency tree `verify` scans every dependency declared in a manifest — ideal for code review. ```bash guarddog npm verify /path/to/repo/package.json ``` ### 4. Focus on the highest-signal heuristics Filter to the npm rules most indicative of malware to cut noise during triage. ```bash guarddog npm scan some-pkg \ --rules npm-install-script \ --rules npm-serialize-environment \ --rules npm-exec-base64 \ --rules npm-silent-process-execution \ --rules npm-obfuscation \ --rules shady-links \ --rules typosquatting ``` ### 5. Emit machine-readable output for pipelines JSON for tooling, SARIF for GitHub code scanning. ```bash guarddog npm scan some-pkg --output-format=json > guarddog.json guarddog npm verify package.json --output-format=sarif > guarddog.sarif ``` ### 6. Manually inspect lifecycle scripts and source Lifecycle scripts are the first thing to read; obfuscation and outbound URLs are red flags. ```bash # Show all lifecycle hooks jq '.scripts' package/package.json # Hunt for exfiltration / execution primitives in the source grep -rEn "child_process|exec\(|spawn|eval\(|Buffer\.from\(.*base64|process\.env|https?://" package/ \ --include='*.js' --include='*.ts' | head -50 ``` ### 7. Cross-check lockfiles against known-malicious versions During an active campaign, compare pinned versions to the advisory's bad-version list, and run OSV. ```bash # Extract resolved name@version pairs from a v3 lockfile jq -r '.packages | to_entries[] | select(.key|startswith("node_modules/")) | "\(.key|ltrimstr("node_modules/"))@\(.value.version)"' package-lock.json # OSV-Scanner flags known-vulnerable AND known-malicious (MAL-) advisories osv-scanner --lockfile=package-lock.json ``` ### 8. Detonate safely with monitoring (only if static is inconclusive) Run the install inside a disposable, network-monitored sandbox. ```bash # In a throwaway container / VM with egress capture running (tcpdump -w capture.pcap): npm install ./some-pkg.tgz # scripts WILL run — sandbox only # Baseline-diff the filesystem afterwards for writes outside node_modules, # and inspect capture.pcap for unexpected DNS / HTTP beacons. ``` ### 9. Extract and operationalize IOCs Pull URLs, IPs, and hashes for blocking and intel sharing. ```bash grep -rhoE "https?://[a-zA-Z0-9./?=_%:-]+" package/ | sort -u > urls.txt sha256sum some-pkg.tgz package/*.js > hashes.txt ``` ### 10. Run the bundled triage helper `agent.py` orchestrates GuardDog, lifecycle-script inspection, and IOC extraction into one report. ```bash python scripts/agent.py --package some-pkg --version 1.2.3 --output verdict.json # or against a local tarball: python scripts/agent.py --tarball ./some-pkg.tgz --output verdict.json ``` ## Tools and Resources | Tool | Purpose | Source | |------|---------|--------| | GuardDog | Heuristic npm/PyPI/Go malware scanner | https://github.com/DataDog/guarddog | | OSV-Scanner | Known-vulnerable & known-malicious (MAL-) advisory matching | https://github.com/google/osv-scanner | | OSV malicious DB | Open-source malicious package advisories | https://github.com/ossf/malicious-packages | | npm lifecycle docs | preinstall/install/postinstall semantics | https://docs.npmjs.com/cli/v10/using-npm/scripts | | Datadog Security Labs | npm campaign writeups & rules | https://securitylabs.datadoghq.com/ | | Semgrep | Rule engine GuardDog uses for source heuristics | https://semgrep.dev/ | ## Validation Criteria - [ ] Package acquired with scripts disabled in an isolated environment. - [ ] GuardDog `scan` run on the target version with results captured. - [ ] Full dependency tree run through GuardDog `verify` where applicable. - [ ] Lifecycle scripts (`preinstall`/`install`/`postinstall`) read and assessed. - [ ] Lockfile versions cross-checked against OSV / known-bad lists. - [ ] Dynamic detonation performed in a sandbox if static analysis was inconclusive. - [ ] IOCs (URLs, IPs, hashes) extracted and recorded. - [ ] Documented verdict (benign / suspicious / malicious) with supporting evidence. - [ ] Malicious findings reported to the registry and shared as threat intel.