@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.
@@ -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
- .action((options) => {
168
- console.log('Scanning lockfile:', options.file);
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.11.6",
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
  }