@lateos/npm-scan 0.11.6 → 0.12.1
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.
- package/.husky/pre-commit +1 -0
- package/README.md +38 -0
- package/cli/cli.js +74 -2
- package/package.json +9 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npx lint-staged
|
package/README.md
CHANGED
|
@@ -70,6 +70,7 @@ Attackers have moved past simple typosquatting. They now ship **obfuscated prein
|
|
|
70
70
|
| 🐳 | **Docker + GitHub Action** | Multi-arch images, one-command Compose pipeline, PR scan action |
|
|
71
71
|
| 🛡️ | **Zero telemetry** | No data leaves your machine. No cloud. No callbacks. |
|
|
72
72
|
| 💾 | **Local scan history** | SQLite-backed persistence, zero external dependencies |
|
|
73
|
+
| 🪝 | **Pre-commit hook** | Block threats before commit — one-liner install, scans `package-lock.json` changes |
|
|
73
74
|
|
|
74
75
|
---
|
|
75
76
|
|
|
@@ -204,6 +205,15 @@ npm-scan scan-lockfile --fail-on low
|
|
|
204
205
|
# Generate SARIF v2.1 output for GitHub Advanced Security / VS Code
|
|
205
206
|
npm-scan scan-lockfile --sarif results.sarif
|
|
206
207
|
|
|
208
|
+
# Watch for changes and auto-rescan (single lockfile)
|
|
209
|
+
npm-scan scan-lockfile --watch
|
|
210
|
+
|
|
211
|
+
# Watch with faster debounce (500ms) — great for dev workflows
|
|
212
|
+
npm-scan scan-lockfile --watch --debounce 500
|
|
213
|
+
|
|
214
|
+
# Watch monorepo (all package-lock.json files in workspace)
|
|
215
|
+
npm-scan scan-lockfile --watch --monorepo
|
|
216
|
+
|
|
207
217
|
# Output only risk score (0-10) for dashboards/thresholds
|
|
208
218
|
npm-scan scan-lockfile --score-only
|
|
209
219
|
```
|
|
@@ -576,6 +586,32 @@ npm-scan report --html > report.html
|
|
|
576
586
|
# path: report.html
|
|
577
587
|
```
|
|
578
588
|
|
|
589
|
+
### Pre-commit hook
|
|
590
|
+
|
|
591
|
+
Block supply chain threats **before** they reach version control — no CI required.
|
|
592
|
+
|
|
593
|
+
```bash
|
|
594
|
+
# One-liner install (requires Node 18+, Git)
|
|
595
|
+
npx husky@latest init && npm install && npx husky add .husky/pre-commit "npx lint-staged"
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
**What it does:** On every `git commit`, lint-staged detects staged changes to `package.json` or `package-lock.json` and runs `npm-scan scan-lockfile --fail-on high`. Commits are blocked if threats are found.
|
|
599
|
+
|
|
600
|
+
```bash
|
|
601
|
+
$ git commit -m "bump lodash"
|
|
602
|
+
✔ Preparing lint-staged configuration...
|
|
603
|
+
✔ Running tasks for staged package*.json files...
|
|
604
|
+
✔ npm-scan scan-lockfile --fail-on high
|
|
605
|
+
🔴 ATK-003: Credential exfiltration (DNS lookup to credentialharvest.example.com)
|
|
606
|
+
🔴 ATK-007: Typosquat detected (lodash@7.7.7)
|
|
607
|
+
⚠ Exiting with code 1 — threat(s) found
|
|
608
|
+
|
|
609
|
+
npm scan • @lateos/npm-scan v0.11.6
|
|
610
|
+
error: Command failed with exit code 1.
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
Add `--no-verify` to bypass for emergencies (`git commit -m "emergency fix" --no-verify`).
|
|
614
|
+
|
|
579
615
|
### Docker
|
|
580
616
|
|
|
581
617
|
See the [Docker quick-start section](#-run-lateosnpm-scan-anywhere-with-docker--zero-installation) above for pull commands, Compose pipeline, and multi-arch images.
|
|
@@ -592,7 +628,9 @@ See the [Docker quick-start section](#-run-lateosnpm-scan-anywhere-with-docker--
|
|
|
592
628
|
- Policy-as-code engine (YAML)
|
|
593
629
|
- Local SQLite scan history
|
|
594
630
|
- GitHub Action
|
|
631
|
+
- Pre-commit hook (husky + lint-staged)
|
|
595
632
|
- Docker images + Compose pipeline
|
|
633
|
+
- Watch mode (--watch / --monorepo for auto-rescan)
|
|
596
634
|
|
|
597
635
|
### Premium (🔐 license key)
|
|
598
636
|
|
package/cli/cli.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { Command } from 'commander';
|
|
4
|
+
import { watch } from 'fs';
|
|
5
|
+
import { statSync } from 'fs';
|
|
6
|
+
import { execSync } from 'child_process';
|
|
7
|
+
import { glob } from 'glob';
|
|
4
8
|
import { isFeatureEnabled, generateKey } from '../backend/license.js';
|
|
5
9
|
|
|
6
10
|
function requirePremium(feature, licenseKey) {
|
|
@@ -164,8 +168,76 @@ program
|
|
|
164
168
|
.option('--fail-on <level>', 'Exit with code 1 if findings >= level (low|medium|high|critical)', 'none')
|
|
165
169
|
.option('--csv [file]', 'Output CSV format to file or stdout')
|
|
166
170
|
.option('--sarif [file]', 'Output SARIF v2.1 format to file or stdout')
|
|
167
|
-
.
|
|
168
|
-
|
|
171
|
+
.option('--watch', 'Watch for changes and re-scan automatically')
|
|
172
|
+
.option('--debounce <ms>', 'Debounce delay in ms before rescanning (default: 1000)', '1000')
|
|
173
|
+
.option('--silent', 'Suppress stdout output (useful for piping)')
|
|
174
|
+
.option('--monorepo', 'Scan all package-lock.json files in workspace')
|
|
175
|
+
.action(async (options) => {
|
|
176
|
+
const silent = options.silent;
|
|
177
|
+
const debounce = parseInt(options.debounce, 10) || 1000;
|
|
178
|
+
const isWatch = options.watch;
|
|
179
|
+
const isMonorepo = options.monorepo;
|
|
180
|
+
|
|
181
|
+
if (isWatch) {
|
|
182
|
+
if (isMonorepo) {
|
|
183
|
+
const lockfiles = await glob('**/package-lock.json', { ignore: 'node_modules/**' });
|
|
184
|
+
|
|
185
|
+
if (!silent) {
|
|
186
|
+
console.log(`\x1b[32m✔\x1b[0m npm-scan watch mode (monorepo) — ${lockfiles.length} lockfiles`);
|
|
187
|
+
console.log(` Debounce: ${debounce}ms | Press Ctrl+C to stop\n`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let timers = {};
|
|
191
|
+
for (const lf of lockfiles) {
|
|
192
|
+
if (!silent) console.log(` Watching: ${lf}`);
|
|
193
|
+
const watcher = watch(lf, (eventType) => {
|
|
194
|
+
if (eventType !== 'change') return;
|
|
195
|
+
clearTimeout(timers[lf]);
|
|
196
|
+
timers[lf] = setTimeout(() => {
|
|
197
|
+
if (!silent) {
|
|
198
|
+
console.log(`\n\x1b[90m[${new Date().toLocaleTimeString()}]\x1b[0m ${lf} changed — scanning...`);
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
execSync(`node cli/cli.js scan-lockfile -f "${lf}" --fail-on ${options.failOn || 'high'} --silent`, { stdio: silent ? 'ignore' : 'inherit' });
|
|
202
|
+
} catch (e) {}
|
|
203
|
+
}, debounce);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
process.on('SIGINT', () => {
|
|
208
|
+
if (!silent) console.log('\n\x1b[33m✖\x1b[0m Stopped.');
|
|
209
|
+
process.exit(0);
|
|
210
|
+
});
|
|
211
|
+
} else {
|
|
212
|
+
const lockfile = options.file;
|
|
213
|
+
let lastSize = 0;
|
|
214
|
+
try { lastSize = statSync(lockfile).size; } catch {}
|
|
215
|
+
|
|
216
|
+
if (!silent) {
|
|
217
|
+
console.log(`\x1b[32m✔\x1b[0m npm-scan watch mode — ${lockfile}`);
|
|
218
|
+
console.log(` Debounce: ${debounce}ms | Press Ctrl+C to stop\n`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const watcher = watch(lockfile, (eventType) => {
|
|
222
|
+
if (eventType !== 'change') return;
|
|
223
|
+
const size = statSync(lockfile).size;
|
|
224
|
+
if (size === lastSize) return;
|
|
225
|
+
lastSize = size;
|
|
226
|
+
if (!silent) console.log(`\n\x1b[90m[${new Date().toLocaleTimeString()}]\x1b[0m ${lockfile} changed — rescanning...`);
|
|
227
|
+
try {
|
|
228
|
+
execSync(`node cli/cli.js scan-lockfile --fail-on ${options.failOn || 'high'} --silent`, { stdio: silent ? 'ignore' : 'inherit' });
|
|
229
|
+
} catch (e) {}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
process.on('SIGINT', () => {
|
|
233
|
+
watcher.close();
|
|
234
|
+
if (!silent) console.log('\n\x1b[33m✖\x1b[0m Stopped.');
|
|
235
|
+
process.exit(0);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
console.log('Scanning lockfile:', options.file);
|
|
240
|
+
}
|
|
169
241
|
});
|
|
170
242
|
|
|
171
243
|
program
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lateos/npm-scan",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "Modern npm supply chain security scanner — detects obfuscated payloads, credential stealers, conditional triggers, sandbox evasion, and worm-like propagation. 11 attack types, SBOM, NIST/EU CRA compliance reporting.",
|
|
5
5
|
"main": "backend/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -29,9 +29,13 @@
|
|
|
29
29
|
"test": "node --test",
|
|
30
30
|
"test:coverage": "node --experimental-test-coverage --test",
|
|
31
31
|
"test:verbose": "node --test --test-reporter spec",
|
|
32
|
+
"prepare": "husky",
|
|
32
33
|
"build": "echo 'Build stub'",
|
|
33
34
|
"corpus": "node tests/corpus/run.js"
|
|
34
35
|
},
|
|
36
|
+
"lint-staged": {
|
|
37
|
+
"**/package{,-lock}.json": "node cli/cli.js scan-lockfile --fail-on high"
|
|
38
|
+
},
|
|
35
39
|
"publishConfig": {
|
|
36
40
|
"access": "public"
|
|
37
41
|
},
|
|
@@ -43,5 +47,9 @@
|
|
|
43
47
|
"pdf-lib": "^1.17.1",
|
|
44
48
|
"sql.js": "^1.11.0",
|
|
45
49
|
"tar": "^7.5.15"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"husky": "^9.1.7",
|
|
53
|
+
"lint-staged": "^16.4.0"
|
|
46
54
|
}
|
|
47
55
|
}
|