npm-scan-plus 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/.eslintrc.json +32 -0
  2. package/.github/CODEOWNERS +3 -0
  3. package/.github/workflows/ci.yml +105 -0
  4. package/.prettierrc +10 -0
  5. package/FUNDING.yml +1 -0
  6. package/PLAN.md +151 -0
  7. package/README.md +150 -0
  8. package/bin/npm-scan +13 -0
  9. package/bin/npm-scan-wrap +100 -0
  10. package/dist/cli/index.d.ts +18 -0
  11. package/dist/cli/index.d.ts.map +1 -0
  12. package/dist/cli/index.js +299 -0
  13. package/dist/cli/index.js.map +1 -0
  14. package/dist/lib/blocklist.d.ts +45 -0
  15. package/dist/lib/blocklist.d.ts.map +1 -0
  16. package/dist/lib/blocklist.js +256 -0
  17. package/dist/lib/blocklist.js.map +1 -0
  18. package/dist/lib/extended.js +314 -0
  19. package/dist/lib/extended.js.map +1 -0
  20. package/dist/lib/integrity.js +247 -0
  21. package/dist/lib/integrity.js.map +1 -0
  22. package/dist/lib/patterns.d.ts +76 -0
  23. package/dist/lib/patterns.d.ts.map +1 -0
  24. package/dist/lib/patterns.js +414 -0
  25. package/dist/lib/patterns.js.map +1 -0
  26. package/dist/lib/registry.d.ts +42 -0
  27. package/dist/lib/registry.d.ts.map +1 -0
  28. package/dist/lib/registry.js +157 -0
  29. package/dist/lib/registry.js.map +1 -0
  30. package/dist/lib/scanner.d.ts +43 -0
  31. package/dist/lib/scanner.d.ts.map +1 -0
  32. package/dist/lib/scanner.js +432 -0
  33. package/dist/lib/scanner.js.map +1 -0
  34. package/dist/lib/vuln.js +284 -0
  35. package/dist/lib/vuln.js.map +1 -0
  36. package/dist/types.d.ts +85 -0
  37. package/dist/types.d.ts.map +1 -0
  38. package/dist/types.js +6 -0
  39. package/dist/types.js.map +1 -0
  40. package/jest.config.js +18 -0
  41. package/package.json +56 -0
  42. package/src/cli/index.ts +336 -0
  43. package/src/lib/blocklist.ts +239 -0
  44. package/src/lib/extended.ts +384 -0
  45. package/src/lib/integrity.ts +253 -0
  46. package/src/lib/patterns.ts +404 -0
  47. package/src/lib/registry.ts +146 -0
  48. package/src/lib/scanner.ts +447 -0
  49. package/src/lib/vuln.ts +321 -0
  50. package/src/types.ts +102 -0
  51. package/tests/blocklist.test.ts +89 -0
  52. package/tests/extended.test.ts +204 -0
  53. package/tests/patterns.test.ts +147 -0
  54. package/tests/scanner.test.ts +116 -0
  55. package/tests/vuln.test.ts +66 -0
  56. package/tsconfig.json +20 -0
package/.eslintrc.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "env": {
3
+ "node": true,
4
+ "es2022": true,
5
+ "jest": true
6
+ },
7
+ "extends": [
8
+ "eslint:recommended",
9
+ "plugin:@typescript-eslint/recommended"
10
+ ],
11
+ "parser": "@typescript-eslint/parser",
12
+ "parserOptions": {
13
+ "ecmaVersion": "latest",
14
+ "sourceType": "module",
15
+ "project": "./tsconfig.json"
16
+ },
17
+ "plugins": [
18
+ "@typescript-eslint"
19
+ ],
20
+ "rules": {
21
+ "no-console": "off",
22
+ "@typescript-eslint/no-explicit-any": "warn",
23
+ "@typescript-eslint/explicit-module-boundary-types": "off",
24
+ "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
25
+ "no-case-declarations": "off"
26
+ },
27
+ "ignorePatterns": [
28
+ "dist/",
29
+ "node_modules/",
30
+ "coverage/"
31
+ ]
32
+ }
@@ -0,0 +1,3 @@
1
+ # Code reviewers for this repository
2
+
3
+ * @cbunt
@@ -0,0 +1,105 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, develop]
6
+ pull_request:
7
+ branches: [main, develop]
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+
13
+ strategy:
14
+ matrix:
15
+ node-version: [18.x, 20.x, 22.x]
16
+
17
+ steps:
18
+ - name: Checkout code
19
+ uses: actions/checkout@v4
20
+
21
+ - name: Setup Node.js ${{ matrix.node-version }}
22
+ uses: actions/setup-node@v4
23
+ with:
24
+ node-version: ${{ matrix.node-version }}
25
+ cache: 'npm'
26
+
27
+ - name: Install dependencies
28
+ run: npm ci
29
+
30
+ - name: Build
31
+ run: npm run build
32
+
33
+ test:
34
+ runs-on: ubuntu-latest
35
+
36
+ strategy:
37
+ matrix:
38
+ node-version: [18.x, 20.x, 22.x]
39
+
40
+ steps:
41
+ - name: Checkout code
42
+ uses: actions/checkout@v4
43
+
44
+ - name: Setup Node.js ${{ matrix.node-version }}
45
+ uses: actions/setup-node@v4
46
+ with:
47
+ node-version: ${{ matrix.node-version }}
48
+ cache: 'npm'
49
+
50
+ - name: Install dependencies
51
+ run: npm ci
52
+
53
+ - name: Build
54
+ run: npm run build
55
+
56
+ - name: Run tests
57
+ run: npm test
58
+
59
+ - name: Upload coverage
60
+ uses: actions/upload-artifact@v4
61
+ if: matrix.node-version == '20.x'
62
+ with:
63
+ name: coverage
64
+ path: coverage/
65
+
66
+ lint:
67
+ runs-on: ubuntu-latest
68
+
69
+ steps:
70
+ - name: Checkout code
71
+ uses: actions/checkout@v4
72
+
73
+ - name: Setup Node.js
74
+ uses: actions/setup-node@v4
75
+ with:
76
+ node-version: 20
77
+ cache: 'npm'
78
+
79
+ - name: Install dependencies
80
+ run: npm ci
81
+
82
+ - name: Run ESLint
83
+ run: npm run lint
84
+
85
+ - name: Check formatting
86
+ run: npm run format -- --check
87
+
88
+ security:
89
+ runs-on: ubuntu-latest
90
+
91
+ steps:
92
+ - name: Checkout code
93
+ uses: actions/checkout@v4
94
+
95
+ - name: Setup Node.js
96
+ uses: actions/setup-node@v4
97
+ with:
98
+ node-version: 20
99
+ cache: 'npm'
100
+
101
+ - name: Install dependencies
102
+ run: npm ci
103
+
104
+ - name: Check for known vulnerabilities
105
+ run: npm audit --audit-level=moderate
package/.prettierrc ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "semi": true,
3
+ "singleQuote": true,
4
+ "tabWidth": 2,
5
+ "trailingComma": "none",
6
+ "printWidth": 100,
7
+ "bracketSpacing": true,
8
+ "arrowParens": "avoid",
9
+ "endOfLine": "lf"
10
+ }
package/FUNDING.yml ADDED
@@ -0,0 +1 @@
1
+ github: cbuntingde
package/PLAN.md ADDED
@@ -0,0 +1,151 @@
1
+ # npm Security Scanner - Implementation Plan
2
+
3
+ ## Project Overview
4
+
5
+ **Project name:** npm-scan
6
+ **Type:** Node.js CLI tool with library
7
+ **Core functionality:** Security scanner that detects malicious npm packages, supply chain attacks, and obfuscated code BEFORE installation, plus post-install forensics
8
+
9
+ ## Architecture
10
+
11
+ ### 1. Pre-Install Scanner (npm-scan pre)
12
+
13
+ Runs BEFORE npm install, intercepts package requests:
14
+
15
+ ```
16
+ User runs: npx npm-scan pre install lodash
17
+ ↓
18
+ Scanner fetches lodash metadata from npm registry
19
+ ↓
20
+ Checks against blocklists (known malicious packages)
21
+ ↓
22
+ Analyzes package publisher reputation
23
+ ↓
24
+ Checks for reported vulnerabilities
25
+ ↓
26
+ Reports threat assessment
27
+ ↓
28
+ Only then runs npm install (if approved)
29
+ ```
30
+
31
+ ### 2. Post-Install Scanner (npm-scan post)
32
+
33
+ Runs AFTER npm install, scans downloaded packages:
34
+
35
+ ```
36
+ User runs: npx npm-scan post
37
+ ↓
38
+ Scanner walks node_modules/
39
+ ↓
40
+ For each package:
41
+ - Detects obfuscated code (base64, hex, eval wrappers)
42
+ - Checks for suspicious patterns (env exfil, crypto mining)
43
+ - Analyzes dependency chain for typosquatting
44
+ - Validates package integrity (hash verification)
45
+ ↓
46
+ Reports findings
47
+ ```
48
+
49
+ ## Technical Implementation
50
+
51
+ ### Core Library Functions
52
+
53
+ 1. **`fetchPackageMetadata(name, registry)`** - Get package info from npm registry
54
+ 2. **`checkBlocklist(package)`** - Verify against known malicious blocklists
55
+ 3. **`analyzePublisher(publisher)`** - Check publisher reputation
56
+ 4. **`detectVulnerabilities(version)`** - Check for known CVEs
57
+ 5. **`scanPackageFiles(tarballPath)`** - Extract and analyze package contents
58
+ 6. **`detectObfuscation(files)`** - Identify obfuscated code patterns
59
+ 7. **`detectSuspiciousPatterns(files)`** - Check for malicious patterns
60
+ 8. **`checkTyposquatting(name, deps)`** - Detect typosquatting in dependency tree
61
+ 9. **`validateIntegrity(package, tarball)`** - Verify checksums
62
+
63
+ ### CLI Commands
64
+
65
+ ```bash
66
+ # Pre-install scan
67
+ npx npm-scan pre install <package> [--version <version>]
68
+
69
+ # Post-install scan
70
+ npx npm-scan post [--folder <path>]
71
+
72
+ # Full scan (both)
73
+ npx npm-scan scan <package> [--full]
74
+
75
+ # Blocklist management
76
+ npx npm-scan blocklist add <package>
77
+ npx npm-scan blocklist remove <package>
78
+ npx npm-scan blocklist list
79
+ ```
80
+
81
+ ### Patterns to Detect
82
+
83
+ **Obfuscation:**
84
+ - Base64 encoded strings in eval()
85
+ - Hex-encoded code
86
+ - String.fromCharCode() usage with eval
87
+ - Packed/minified with suspicious wrappers
88
+
89
+ **Malicious behavior:**
90
+ - Network requests to suspicious IPs/domains
91
+ - Environment variable exfiltration
92
+ - File system access beyond package scope
93
+ - Child process spawning
94
+ - Crypto mining detection
95
+ - Credential theft patterns
96
+ - .bash_history, .gitconfig access
97
+
98
+ **Supply chain attacks:**
99
+ - Typosquatting (lodash vs lodsh)
100
+ - Dependency confusion
101
+ - Unexpected peer dependencies
102
+ - Suspicious postinstall scripts
103
+
104
+ ## File Structure
105
+
106
+ ```
107
+ npm-scan/
108
+ ā”œā”€ā”€ src/
109
+ │ ā”œā”€ā”€ lib/
110
+ │ │ ā”œā”€ā”€ registry.ts # npm registry API client
111
+ │ │ ā”œā”€ā”€ blocklist.ts # Blocklist management
112
+ │ │ ā”œā”€ā”€ scanner.ts # Core scanning logic
113
+ │ │ ā”œā”€ā”€ patterns.ts # Detection patterns
114
+ │ │ └── integrity.ts # Hash verification
115
+ │ ā”œā”€ā”€ cli/
116
+ │ │ ā”œā”€ā”€ pre.ts # Pre-install command
117
+ │ │ ā”œā”€ā”€ post.ts # Post-install command
118
+ │ │ └── index.ts # CLI entry
119
+ │ └── types.ts
120
+ ā”œā”€ā”€ bin/
121
+ │ └── npm-scan
122
+ ā”œā”€ā”€ tests/
123
+ │ └── ...
124
+ ā”œā”€ā”€ package.json
125
+ ā”œā”€ā”€ tsconfig.json
126
+ ā”œā”€ā”€ README.md
127
+ └── .npmignore
128
+ ```
129
+
130
+ ## Blocklist Sources (MVP)
131
+
132
+ - npm Advisory DB (via npm audit API)
133
+ - GitHub Advisory Database
134
+ - Known malicious packages (maintained locally)
135
+ - User-defined blocklist
136
+
137
+ ## Integration Options
138
+
139
+ 1. **Standalone CLI:**
140
+ ```bash
141
+ npx npm-scan pre install <package>
142
+ ```
143
+
144
+ 2. **npm hook wrapper:**
145
+ Place script in PATH before npm to intercept
146
+
147
+ 3. **Git hook integration:**
148
+ Run in pre-commit hook
149
+
150
+ 4. **CI/CD integration:**
151
+ Use in pipeline before deployment
package/README.md ADDED
@@ -0,0 +1,150 @@
1
+ # npm-scan šŸ”’
2
+
3
+ **Security scanner for npm packages** - Pre and post-install scanning for malicious code, supply chain attacks, and obfuscated code.
4
+
5
+ ## Why We Built This
6
+
7
+ npm package supply chain attacks are increasing at an alarming rate. Recent examples include:
8
+
9
+ - **TanStack (May 2026)**: Malicious package published to npm registry containing cryptocurrency stealing code distributed to thousands of applications. ([InfoQ](https://www.infoq.com/news/2026/05/tanstack-supply-chain-attack/))
10
+ - **event-stream (2018)**: Maintainer deliberately added malicious code to steal cryptocurrency wallet keys from Copay users
11
+ - **ua-parser-js (2021)**: Compromised package with cryptomining malware affecting millions of downloads
12
+ - **Colors.js / Faker.js (2022)**: Maintainer intentionally sabotaged popular packages
13
+
14
+ These attacks succeed because:
15
+ - Developers trust npm packages without verification
16
+ - No automated scanning before install
17
+ - Obfuscated code hides malicious intent
18
+ - Typosquatting confuses developers
19
+
20
+ **npm-scan** was built to automatically detect these threats before they reach your project.
21
+
22
+ ## Features
23
+
24
+ ### Pre-Install Scanning
25
+ - āœ… **Blocklist Check** - Known malicious packages (event-stream, flatmap-stream, etc.)
26
+ - āœ… **Typosquatting Detection** - Similar names to popular packages (lodash vs lodsh)
27
+ - āœ… **Vulnerability Database Check**
28
+ - OSV (Google's Open Source Vulnerabilities)
29
+ - GitHub Advisory Database
30
+ - npm Audit
31
+ - āœ… **License Risk Analysis** - Warns about GPL, proprietary, or missing licenses
32
+ - āœ… **Maintainer Trust Scoring** - Identifies known trusted maintainers
33
+ - āœ… **Repository Validation** - Verifies repo URL matches package
34
+ - āœ… **Package Integrity** - Hash verification from npm registry
35
+ - āœ… **Size Anomaly Detection** - Flags packages > 50MB
36
+ - āœ… **Deprecated Dependencies** - Warns about request, moment, underscore
37
+
38
+ ### Post-Install Scanning
39
+ - āœ… **Obfuscation Detection** - base64, eval(), hex encoding
40
+ - āœ… **Malicious Pattern Detection** - env exfil, shell exec, crypto mining
41
+ - āœ… **Suspicious Scripts** - postinstall, preinstall analysis
42
+ - āœ… **Sensitive Files** - .env, .ssh, credentials detection
43
+
44
+ ## Installation
45
+
46
+ ```bash
47
+ npm install -g npm-scan
48
+ ```
49
+
50
+ Or use without installation:
51
+
52
+ ```bash
53
+ npx npm-scan pre install <package>
54
+ ```
55
+
56
+ ## Quick Start: Automatic Wrapper
57
+
58
+ The **recommended way** to use npm-scan is with the automatic wrapper:
59
+
60
+ ```bash
61
+ # Install a package with automatic pre + post scan
62
+ npm-scan-wrap install lodash
63
+
64
+ # Install multiple packages
65
+ npm-scan-wrap install lodash axios express
66
+
67
+ # Install all dependencies from package.json
68
+ npm-scan-wrap install
69
+ ```
70
+
71
+ The wrapper automatically:
72
+ 1. šŸ” Pre-install scans each package
73
+ 2. šŸ“„ Runs npm install
74
+ 3. šŸ” Post-install scans node_modules
75
+
76
+ ## Manual Usage
77
+
78
+ If you prefer manual control:
79
+
80
+ ### Pre-install scan
81
+ ```bash
82
+ npm-scan pre install <package>
83
+ npm-scan pre install axios --version 1.6.0
84
+ npm-scan pre install lodash -V # verbose output
85
+ ```
86
+
87
+ ### Post-install scan
88
+ ```bash
89
+ npm-scan post
90
+ npm-scan post --folder ./node_modules
91
+ ```
92
+
93
+ ### Blocklist management
94
+ ```bash
95
+ npm-scan blocklist list
96
+ npm-scan blocklist add <package>
97
+ npm-scan blocklist remove <package>
98
+ ```
99
+
100
+ ## Detection Patterns
101
+
102
+ ### Obfuscation
103
+ - `eval()` with atob/fromCharCode
104
+ - Base64 encoded strings
105
+ - Hex/unicode encoded characters
106
+
107
+ ### Malicious Behavior
108
+ - Environment variable access (KEYS, SECRETS, TOKENS)
109
+ - Network requests to IP addresses or external code hosting
110
+ - Child process execution
111
+ - Crypto mining pool connections
112
+ - Keylogging code
113
+
114
+ ### Suspicious Scripts
115
+ - postinstall/preinstall with complex shell commands
116
+ - curl/wget downloads
117
+ - Packages scanning directories outside scope
118
+
119
+ ## Environment Variables
120
+
121
+ - `GITHUB_TOKEN` - For higher GitHub Advisory API rate limits
122
+
123
+ ## Development
124
+
125
+ ```bash
126
+ # Build
127
+ npm run build
128
+
129
+ # Test
130
+ npm test
131
+
132
+ # Lint
133
+ npm run lint
134
+ ```
135
+
136
+ ## Security Threats Detected
137
+
138
+ | Threat Type | Example |
139
+ |------------|---------|
140
+ | Blocklisted | event-stream, flatmap-stream |
141
+ | Typosquatting | lodsh (looks like lodash) |
142
+ | Vulnerabilities | CVE-2021-23337, GHSA-xxxx |
143
+ | Obfuscation | eval(atob(...)) |
144
+ | Malicious Code | process.env.API_KEY exfil |
145
+ | Suspicious Scripts | postinstall: curl ... |
146
+ | Dependency Issues | Deprecated packages, large trees |
147
+
148
+ ## License
149
+
150
+ MIT
package/bin/npm-scan ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * npm-scan CLI entry point
5
+ * Pre and post-install security scanner for npm packages
6
+ */
7
+
8
+ const { run } = require('../dist/cli/index');
9
+
10
+ run(process.argv).catch(err => {
11
+ console.error('npm-scan error:', err.message);
12
+ process.exit(1);
13
+ });
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * npm-scan wrapper - Automatically scans before and after npm install
5
+ * Usage: npm-scan-wrap install <packages>
6
+ * npm-scan-wrap install
7
+ */
8
+
9
+ const { spawn } = require('child_process');
10
+ const path = require('path');
11
+ const fs = require('fs');
12
+
13
+ const SCANNER_PATH = path.join(__dirname, '../dist/cli/index.js');
14
+
15
+ async function runScanner(args) {
16
+ return new Promise((resolve, reject) => {
17
+ const scanner = spawn('node', [SCANNER_PATH, ...args], {
18
+ stdio: 'inherit',
19
+ shell: true
20
+ });
21
+
22
+ scanner.on('close', (code) => {
23
+ resolve(code);
24
+ });
25
+ scanner.on('error', reject);
26
+ });
27
+ }
28
+
29
+ async function runNpm(args) {
30
+ return new Promise((resolve, reject) => {
31
+ const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
32
+ const install = spawn(npm, args, {
33
+ stdio: 'inherit',
34
+ shell: true,
35
+ env: { ...process.env, NPM_CONFIG_AUDIT: 'false' }
36
+ });
37
+
38
+ install.on('close', (code) => {
39
+ resolve(code);
40
+ });
41
+ install.on('error', reject);
42
+ });
43
+ }
44
+
45
+ async function main() {
46
+ const args = process.argv.slice(2);
47
+
48
+ if (args.length === 0 || args[0] === 'install') {
49
+ console.log('\nšŸ”’ npm-scan: Running automatic pre-install scan...\n');
50
+
51
+ // Run pre-install scan for all packages being installed
52
+ const packages = args.slice(1);
53
+
54
+ if (packages.length > 0) {
55
+ // Scan each package
56
+ for (const pkg of packages) {
57
+ console.log(`\nšŸ“¦ Scanning: ${pkg}\n`);
58
+ await runScanner(['pre', 'install', pkg]);
59
+ }
60
+ } else {
61
+ // Scan package.json dependencies
62
+ const pkgJsonPath = path.join(process.cwd(), 'package.json');
63
+ if (fs.existsSync(pkgJsonPath)) {
64
+ try {
65
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
66
+ const allDeps = [
67
+ ...Object.keys(pkgJson.dependencies || {}),
68
+ ...Object.keys(pkgJson.devDependencies || {})
69
+ ];
70
+
71
+ console.log(`\nšŸ“¦ Scanning ${allDeps.length} dependencies from package.json...\n`);
72
+
73
+ for (const pkg of allDeps) {
74
+ await runScanner(['pre', 'install', pkg]);
75
+ }
76
+ } catch (e) {
77
+ console.log('Could not read package.json, skipping dependency scan');
78
+ }
79
+ }
80
+ }
81
+
82
+ // Run npm install
83
+ console.log('\nšŸ“„ Running npm install...\n');
84
+ await runNpm(['install', ...args.slice(1)]);
85
+
86
+ // Run post-install scan
87
+ console.log('\nšŸ” Running post-install scan...\n');
88
+ await runScanner(['post']);
89
+
90
+ console.log('\nāœ… npm-scan: Complete!\n');
91
+ } else if (args[0] === 'run') {
92
+ // Just run npm run for scripts
93
+ await runNpm(args);
94
+ } else {
95
+ // Pass through other commands
96
+ await runNpm(args);
97
+ }
98
+ }
99
+
100
+ main().catch(console.error);
@@ -0,0 +1,18 @@
1
+ /**
2
+ * CLI entry point
3
+ * Pre and post-install npm security scanner
4
+ */
5
+ /**
6
+ * Main CLI runner
7
+ */
8
+ export declare function run(argv: string[]): Promise<void>;
9
+ /**
10
+ * Print help
11
+ */
12
+ declare function printHelp(): void;
13
+ export { run, printHelp };
14
+ declare const _default: {
15
+ run: typeof run;
16
+ };
17
+ export default _default;
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH;;GAEG;AACH,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBvD;AAiRD;;GAEG;AACH,iBAAS,SAAS,IAAI,IAAI,CAyBzB;AAED,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC;;;;AAC1B,wBAAuB"}