@lateos/npm-scan 0.3.1 → 0.3.3

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
@@ -1,6 +1,6 @@
1
1
  # npm-scan
2
2
 
3
- Powerful npm supply chain security scanner. Detects malicious packages, supply chain attacks, and generates SBOM reports.
3
+ Powerful npm supply chain security scanner. Detects malicious packages, supply chain attacks, and generates SBOM + compliance reports.
4
4
 
5
5
  ## Quick Start
6
6
 
@@ -17,56 +17,66 @@ npx @lateos/npm-scan scan lodash
17
17
 
18
18
  ## Features
19
19
 
20
- - **Static Analysis** — detects malicious lifecycle scripts, obfuscated payloads, credential harvesting, persistence, network exfiltration, dependency confusion, and typosquatting (ATK-001–007)
21
- - **SBOM Output** — CycloneDX 1.5 JSON/XML with findings mapped as vulnerabilities
20
+ - **Static Analysis** — detects malicious lifecycle scripts, obfuscated payloads, credential harvesting, persistence, network exfiltration, dependency confusion, typosquatting, tarball tampering, conditional triggers, and sandbox evasion (ATK-001–010)
21
+ - **SBOM Output** — CycloneDX 1.5 and SPDX 2.3 with findings mapped as vulnerabilities
22
+ - **NIST 800-161 Compliance** — HTML report includes control traceability matrix (SR-2.1 → SR-10.3)
22
23
  - **SQLite Storage** — local scan history, zero external dependencies
23
- - **CLI** — `scan`, `scan-lockfile`, `report --sbom`
24
+ - **CLI** — `scan`, `scan-lockfile`, `report --sbom --html --nist`
25
+ - **Dynamic Sandbox** — gVisor-based isolation (premium, documented in `docs/sandbox-threat-model.md`)
24
26
  - **GitHub Action** — scans lockfile on PRs
25
27
  - **Docker** — multi-arch images via GHCR
26
28
 
27
29
  ## Commands
28
30
 
29
31
  ```
30
- npm-scan scan <package> Scan a package from the npm registry
31
- npm-scan scan-lockfile Scan a local package-lock.json
32
- npm-scan report List recent scans
33
- npm-scan report -i <id> Show findings for a scan
34
- npm-scan report -i <id> --sbom Generate CycloneDX SBOM (json/xml)
32
+ npm-scan scan <package> Scan a package from the npm registry
33
+ npm-scan scan <package> --sbom Scan + output CycloneDX SBOM
34
+ npm-scan scan <package> --sbom spdx Scan + output SPDX SBOM
35
+ npm-scan scan-lockfile Scan a local package-lock.json
36
+ npm-scan report List recent scans
37
+ npm-scan report -i <id> Show findings for a scan
38
+ npm-scan report -i <id> --sbom Generate CycloneDX SBOM
39
+ npm-scan report -i <id> --sbom spdx Generate SPDX SBOM
40
+ npm-scan report -i <id> --html Generate HTML report (with NIST table)
41
+ npm-scan report --html Generate HTML report for all scans
35
42
  ```
36
43
 
37
44
  ## Architecture
38
45
 
39
46
  ```
40
47
  cli/ Commander.js CLI entrypoint
41
- backend/ Detectors, fetch, SQLite db, SBOM, license
48
+ backend/ Detectors, fetch, SQLite db, SBOM, report
42
49
  docker/ Multi-arch Docker images + compose
43
- docs/ Project plan, attack taxonomy (ATK)
44
- tests/ Corpus: clean + malicious packages
50
+ docs/ Project plan, attack taxonomy (ATK), sandbox threat model
51
+ tests/ Corpus: 5 clean + 30 malicious packages
45
52
  ```
46
53
 
54
+ ## Detectors (ATK Taxonomy)
55
+
56
+ | ID | Class | Severity |
57
+ |---------|--------------------------------------------|----------|
58
+ | ATK-001 | Malicious lifecycle scripts | high |
59
+ | ATK-002 | Obfuscated payloads | medium |
60
+ | ATK-003 | Credential harvesting | high |
61
+ | ATK-004 | Persistence via editor configs | high |
62
+ | ATK-005 | Network exfiltration | critical |
63
+ | ATK-006 | Dependency confusion | medium |
64
+ | ATK-007 | Typosquatting | low |
65
+ | ATK-008 | Tarball tampering (published ≠ source) | high |
66
+ | ATK-009 | Conditional/dormant triggers (CI, time) | high |
67
+ | ATK-010 | Sandbox evasion / anti-analysis | medium |
68
+
69
+ See `docs/attack-taxonomy.md` for full NIST 800-161 mappings, evasion surfaces, and PoC examples.
70
+
47
71
  ## Development
48
72
 
49
73
  ```bash
50
74
  npm install
51
75
  npm run dev # CLI stub
52
- npm run test # Unit tests
53
- npm run corpus # False-positive corpus test
76
+ npm run test # Unit tests (13)
77
+ npm run corpus # False-positive corpus test (30 malicious, 5 clean)
54
78
  ```
55
79
 
56
- ## Detectors (ATK Taxonomy)
57
-
58
- | ID | Class | Severity |
59
- |----|-------|----------|
60
- | ATK-001 | Malicious lifecycle scripts | high |
61
- | ATK-002 | Obfuscated payloads | medium |
62
- | ATK-003 | Credential harvesting | high |
63
- | ATK-004 | Persistence via editor configs | high |
64
- | ATK-005 | Network exfiltration | critical |
65
- | ATK-006 | Dependency confusion | medium |
66
- | ATK-007 | Typosquatting | low |
67
-
68
- See `docs/attack-taxonomy.md` for full NIST 800-161 mappings.
69
-
70
80
  ## License
71
81
 
72
- Apache-2.0 core + Commons Clause premium. See `LICENSING.md`.
82
+ Apache-2.0 core + Commons Clause premium. See `LICENSING.md`.
package/backend/db.js CHANGED
@@ -33,6 +33,10 @@ export function getFindings(scanId) {
33
33
  return db.prepare('SELECT * FROM findings WHERE scan_id = ?').all(scanId);
34
34
  }
35
35
 
36
+ export function getScan(scanId) {
37
+ return db.prepare('SELECT * FROM scans WHERE id = ?').get(scanId);
38
+ }
39
+
36
40
  export function close() {
37
41
  db.close();
38
42
  }
package/backend/report.js CHANGED
@@ -6,7 +6,7 @@ export function generateHTML(scans) {
6
6
  const worstLabel = ['', 'info', 'low', 'medium', 'high', 'critical'][worst] || 'clean';
7
7
  const color = { critical: '#d73a49', high: '#cb2431', medium: '#f66a0a', low: '#dbab09', clean: '#28a745' }[worstLabel] || '#28a745';
8
8
  const findingRows = findings.map(f =>
9
- `<tr><td>${f.id}</td><td style="color:${color}">${f.severity}</td><td>${f.title || ''}</td><td>${(f.evidence || '').slice(0, 80)}</td></tr>`
9
+ `<tr><td>${f.atk_id || f.id}</td><td style="color:${color}">${f.severity}</td><td>${f.description || f.title || ''}</td><td>${(f.evidence || '').slice(0, 80)}</td></tr>`
10
10
  ).join('');
11
11
  return { name: s.package_name, worstLabel, color, count: findings.length, findingRows };
12
12
  });
@@ -65,7 +65,7 @@ th { background: #161b22; font-weight: 600; }
65
65
  <h2>NIST SP 800-161 Compliance Summary</h2>
66
66
  ${nistMap}
67
67
 
68
- <p class="meta">npm-scan v0.2.5 | Apache-2.0 + Commons Clause | NIST SP 800-161 mapped</p>
68
+ <p class="meta">npm-scan v${process.env.npm_package_version || '0.3.2'} | Apache-2.0 + Commons Clause | NIST SP 800-161 mapped</p>
69
69
  </body>
70
70
  </html>`;
71
71
  }
@@ -74,8 +74,9 @@ function getAtkFindings(scans) {
74
74
  const map = {};
75
75
  for (const s of scans) {
76
76
  for (const f of (s.findings || [])) {
77
- if (!map[f.id]) map[f.id] = [];
78
- map[f.id].push(f);
77
+ const key = f.atk_id || f.id;
78
+ if (!map[key]) map[key] = [];
79
+ map[key].push(f);
79
80
  }
80
81
  }
81
82
  return map;
package/backend/sbom.js CHANGED
@@ -15,7 +15,7 @@ function generateCycloneDX(pkgJson, findings) {
15
15
  version: pkgJson.version || 'unknown',
16
16
  purl: `pkg:npm/${pkgJson.name || 'unknown'}@${pkgJson.version || 'unknown'}`
17
17
  },
18
- tools: [{ name: 'npm-scan', version: '0.2.5' }]
18
+ tools: [{ name: 'npm-scan', version: '0.3.2' }]
19
19
  },
20
20
  vulnerabilities: findings.map(f => {
21
21
  const atkId = f.atk_id || f.id;
@@ -60,7 +60,7 @@ function generateSPDX(pkgJson, findings) {
60
60
  annotationDate: new Date().toISOString(),
61
61
  annotationType: 'OTHER',
62
62
  annotator: 'Tool: npm-scan',
63
- comment: `[${f.id}] ${f.severity.toUpperCase()}: ${f.title || ''} — ${f.description || ''}`
63
+ comment: `[${f.atk_id || f.id}] ${f.severity.toUpperCase()}: ${f.description || f.title || ''}`
64
64
  }))
65
65
  };
66
66
  return JSON.stringify(spdx, null, 2);
package/cli/cli.js CHANGED
@@ -50,17 +50,20 @@ program
50
50
  .option('--html', 'HTML report')
51
51
  .option('--nist', 'NIST 800-161 compliance report')
52
52
  .action(async (options) => {
53
- const { getRecentScans, getFindings, db } = await import('../backend/db.js');
53
+ const { getRecentScans, getFindings, getScan } = await import('../backend/db.js');
54
54
  if (options.id) {
55
55
  const findings = getFindings(options.id);
56
- const pkg = { name: 'scanned-pkg', version: 'unknown' };
56
+ const scanInfo = getScan(options.id);
57
+ const pkgName = scanInfo?.package_name || 'scan-' + options.id;
58
+ const pkgVer = scanInfo?.version || 'unknown';
59
+ const pkg = { name: pkgName, version: pkgVer };
57
60
  if (options.sbom) {
58
61
  const { generateSBOM } = await import('../backend/sbom.js');
59
62
  const sbom = generateSBOM(pkg, findings, options.sbom === true ? 'json' : options.sbom);
60
63
  console.log(sbom);
61
64
  } else if (options.html || options.nist) {
62
65
  const { generateHTML } = await import('../backend/report.js');
63
- const scan = findings.length ? { package_name: 'scan-' + options.id, findings } : null;
66
+ const scan = findings.length ? { package_name: pkgName, version: pkgVer, findings } : null;
64
67
  const html = generateHTML(scan ? [scan] : []);
65
68
  console.log(html);
66
69
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lateos/npm-scan",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Powerful npm supply chain security scanner - detects malicious packages (Shai-Hulud style), behavioral analysis, SBOM, and compliance reporting.",
5
5
  "main": "backend/index.js",
6
6
  "bin": {