@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 +39 -29
- package/backend/db.js +4 -0
- package/backend/report.js +5 -4
- package/backend/sbom.js +2 -2
- package/cli/cli.js +6 -3
- package/package.json +1 -1
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
|
|
21
|
-
- **SBOM Output** — CycloneDX 1.5
|
|
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>
|
|
31
|
-
npm-scan scan
|
|
32
|
-
npm-scan
|
|
33
|
-
npm-scan
|
|
34
|
-
npm-scan report
|
|
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,
|
|
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
|
|
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
|
-
|
|
78
|
-
map[
|
|
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
|
|
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.
|
|
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,
|
|
53
|
+
const { getRecentScans, getFindings, getScan } = await import('../backend/db.js');
|
|
54
54
|
if (options.id) {
|
|
55
55
|
const findings = getFindings(options.id);
|
|
56
|
-
const
|
|
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:
|
|
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.
|
|
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": {
|