agent-security-scanner-mcp 4.1.0 → 4.1.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/README.md CHANGED
@@ -63,6 +63,8 @@ Continue reading below for full version documentation →
63
63
 
64
64
  ---
65
65
 
66
+ > **New in v4.1.0:** SBOM generation and dependency vulnerability analysis — generates CycloneDX v1.5 SBOMs, scans against OSV.dev for CVEs, detects hallucinated packages, compares baselines, and generates HTML audit reports. Supports 8 lock file formats and 7 manifest formats across npm, Python, Go, Rust, Ruby, and Java ecosystems. [See SBOM Tools](#-sbom--supply-chain-analysis-new-in-v410).
67
+ >
66
68
  > **New in v4.0.0:** LLM-powered semantic code review agent with intent profiling — understands what your project is supposed to do and flags patterns that violate that intent. Same `eval()` call = safe in a build tool, dangerous in an e-commerce app. Supports Claude CLI (no API key needed!), Anthropic, and OpenAI. [See code-review-agent](#-llm-powered-code-review-agent-new-in-v400).
67
69
  >
68
70
  > **New in v3.11.0:** ClawHub ecosystem security scanning — scanned all 16,532 ClawHub skills and found 46% have critical vulnerabilities. New `scan-clawhub` CLI for batch scanning, 40+ prompt injection patterns, jailbreak detection (DAN mode, dev mode), data exfiltration checks. [See ClawHub Security Dashboard](https://www.proof-layer.com/dashboard).
@@ -87,6 +89,11 @@ Continue reading below for full version documentation →
87
89
  | `scan_skill` | Deep security scan of an OpenClaw skill: prompt injection, AST+taint code analysis, ClawHavoc malware signatures, supply chain, rug pull. Returns A-F grade | Before installing any OpenClaw skill |
88
90
  | `scanner_health` | Check plugin health: engine status, daemon status, package data availability | Diagnostics and plugin status |
89
91
  | `list_security_rules` | List available security rules and fix templates | To check rule coverage for a language |
92
+ | `sbom_generate` | Generate CycloneDX v1.5 SBOM for a project (8 lock file formats, 7 manifest formats) | Before releases, for compliance audits |
93
+ | `sbom_scan_vulnerabilities` | Cross-reference SBOM against OSV.dev for CVEs with severity filtering | After generating SBOM, for security audits |
94
+ | `sbom_check_hallucinations` | Verify all SBOM packages exist in official registries | Before deploying, to catch AI-invented packages |
95
+ | `sbom_diff` | Compare current SBOM against baseline, detect added/removed/changed packages | In CI/CD to track dependency drift |
96
+ | `sbom_export_report` | Generate HTML or JSON audit report from SBOM with vulnerability data | For PCI-DSS compliance, security reviews |
90
97
 
91
98
  ## Quick Start
92
99
 
@@ -243,6 +250,180 @@ npx cr-agent analyze ./path/to/project -f sarif -p claude-cli
243
250
 
244
251
  ---
245
252
 
253
+ ## 📦 SBOM / Supply Chain Analysis (New in v4.1.0)
254
+
255
+ Generate Software Bill of Materials (SBOM) and analyze dependencies for vulnerabilities across your entire supply chain.
256
+
257
+ ### Quick Start
258
+
259
+ ```bash
260
+ # Generate SBOM for current project
261
+ npx agent-security-scanner-mcp sbom-generate .
262
+
263
+ # Scan for vulnerabilities against OSV.dev
264
+ npx agent-security-scanner-mcp sbom-vulnerabilities .
265
+
266
+ # Check for hallucinated packages
267
+ npx agent-security-scanner-mcp sbom-check-hallucinations .
268
+
269
+ # Compare against baseline (CI/CD)
270
+ npx agent-security-scanner-mcp sbom-diff . --save-baseline # First run
271
+ npx agent-security-scanner-mcp sbom-diff . # Subsequent runs
272
+
273
+ # Generate HTML audit report
274
+ npx agent-security-scanner-mcp sbom-report . --format html
275
+ ```
276
+
277
+ ### Supported Ecosystems
278
+
279
+ | Ecosystem | Lock Files | Manifests | CLI Fallback |
280
+ |-----------|------------|-----------|--------------|
281
+ | **npm** | package-lock.json (v2/v3), yarn.lock (classic/berry), pnpm-lock.yaml | package.json | `npm ls`, `pnpm list` |
282
+ | **Python** | poetry.lock, Pipfile.lock | requirements.txt, pyproject.toml | — |
283
+ | **Go** | go.sum | go.mod | `go list` |
284
+ | **Rust** | Cargo.lock | — | `cargo metadata` |
285
+ | **Ruby** | Gemfile.lock | Gemfile | — |
286
+ | **Java** | — | pom.xml, build.gradle | `mvn dependency:tree` |
287
+
288
+ ### SBOM Tools
289
+
290
+ #### `sbom_generate`
291
+
292
+ Generate a CycloneDX v1.5 SBOM for a project. Discovers all dependencies (direct + transitive) from lock files and manifests.
293
+
294
+ ```json
295
+ // Input
296
+ { "directory_path": "./my-project", "verbosity": "compact" }
297
+
298
+ // Output
299
+ {
300
+ "total_components": 212,
301
+ "direct": 20,
302
+ "dev": 91,
303
+ "ecosystems": ["npm", "pypi"],
304
+ "components": [
305
+ { "name": "express", "version": "4.18.2", "ecosystem": "npm", "isDirect": true }
306
+ ]
307
+ }
308
+ ```
309
+
310
+ #### `sbom_scan_vulnerabilities`
311
+
312
+ Cross-reference SBOM components against OSV.dev vulnerability database. Returns CVE IDs, CVSS scores, severity, and fix recommendations.
313
+
314
+ ```json
315
+ // Input
316
+ { "directory_path": "./my-project", "severity_threshold": "medium" }
317
+
318
+ // Output
319
+ {
320
+ "total_vulnerabilities": 3,
321
+ "by_severity": { "critical": 1, "high": 1, "medium": 1 },
322
+ "vulnerabilities": [
323
+ {
324
+ "id": "GHSA-xxxx-yyyy-zzzz",
325
+ "package": "lodash",
326
+ "severity": "critical",
327
+ "cvss": 9.8,
328
+ "fixed_version": "4.17.21"
329
+ }
330
+ ]
331
+ }
332
+ ```
333
+
334
+ #### `sbom_check_hallucinations`
335
+
336
+ Check all packages in an SBOM against official registries to detect AI-invented package names.
337
+
338
+ ```json
339
+ // Input
340
+ { "directory_path": "./my-project" }
341
+
342
+ // Output
343
+ {
344
+ "total_checked": 212,
345
+ "hallucinated_count": 1,
346
+ "unsupported_ecosystems": ["go", "java"],
347
+ "hallucinated": [
348
+ { "name": "react-async-utils-helper", "ecosystem": "npm" }
349
+ ]
350
+ }
351
+ ```
352
+
353
+ #### `sbom_diff`
354
+
355
+ Compare current project SBOM against a stored baseline. Detects added, removed, and version-changed packages.
356
+
357
+ ```json
358
+ // Input (first run)
359
+ { "directory_path": "./my-project", "save_baseline": true }
360
+
361
+ // Output
362
+ { "message": "Baseline saved to .scanner/sbom-baseline.json" }
363
+
364
+ // Input (subsequent runs)
365
+ { "directory_path": "./my-project" }
366
+
367
+ // Output
368
+ {
369
+ "added": [{ "name": "lodash", "version": "4.17.21", "ecosystem": "npm" }],
370
+ "removed": [],
371
+ "changed": [{ "name": "express", "from": "4.17.1", "to": "4.18.2" }]
372
+ }
373
+ ```
374
+
375
+ #### `sbom_export_report`
376
+
377
+ Generate an HTML or JSON audit report from SBOM data, optionally enriched with vulnerability scan results.
378
+
379
+ ```json
380
+ // Input
381
+ {
382
+ "directory_path": "./my-project",
383
+ "format": "html",
384
+ "include_vulnerabilities": true,
385
+ "output_path": "./sbom-report.html"
386
+ }
387
+
388
+ // Output
389
+ {
390
+ "report_path": "./sbom-report.html",
391
+ "components": 212,
392
+ "vulnerabilities": 3
393
+ }
394
+ ```
395
+
396
+ ### CLI Commands
397
+
398
+ ```bash
399
+ # Generate SBOM
400
+ sbom-generate <dir> [--save] [--output <path>] [--verbosity minimal|compact|full]
401
+
402
+ # Scan vulnerabilities
403
+ sbom-vulnerabilities <dir> [--sbom-path <path>] [--verbosity minimal|compact|full]
404
+
405
+ # Check hallucinations
406
+ sbom-check-hallucinations <dir> [--verbosity minimal|compact|full]
407
+
408
+ # Compare baseline
409
+ sbom-diff <dir> [--save-baseline] [--baseline-path <path>] [--verbosity minimal|compact|full]
410
+
411
+ # Generate report
412
+ sbom-report <dir> [--format html|json] [--output <path>] [--no-vulnerabilities]
413
+ ```
414
+
415
+ ### Features
416
+
417
+ - **CycloneDX v1.5 JSON** — Industry-standard SBOM format
418
+ - **OSV.dev Integration** — Real-time vulnerability data with 24-hour local cache
419
+ - **Multi-Ecosystem** — Single scan discovers dependencies across all package managers
420
+ - **Direct vs Transitive** — Distinguishes direct dependencies from transitive ones
421
+ - **Dev Dependencies** — Optionally include/exclude development dependencies
422
+ - **Baseline Comparison** — Track dependency drift over time
423
+ - **HTML Reports** — Visual dashboard with severity charts for compliance audits
424
+
425
+ ---
426
+
246
427
  ## Tool Reference
247
428
 
248
429
  ### `scan_security`
@@ -1158,7 +1339,7 @@ AI coding agents introduce attack surfaces that traditional security tools weren
1158
1339
  |----------|-------|
1159
1340
  | **Transport** | stdio |
1160
1341
  | **Package** | `agent-security-scanner-mcp` (npm) |
1161
- | **Tools** | 12 |
1342
+ | **Tools** | 17 |
1162
1343
  | **Languages** | 12 |
1163
1344
  | **Ecosystems** | 7 |
1164
1345
  | **Auth** | None required |
@@ -1240,6 +1421,22 @@ All MCP tools support a `verbosity` parameter to minimize context window consump
1240
1421
 
1241
1422
  ## Changelog
1242
1423
 
1424
+ ### v4.1.0 (2026-03-27) - SBOM Generation & Vulnerability Analysis
1425
+
1426
+ **🚀 New Feature: Software Bill of Materials (SBOM)**
1427
+
1428
+ - **5 New MCP Tools:** `sbom_generate`, `sbom_scan_vulnerabilities`, `sbom_check_hallucinations`, `sbom_diff`, `sbom_export_report`
1429
+ - **CycloneDX v1.5:** Industry-standard SBOM format output
1430
+ - **8 Lock File Parsers:** package-lock.json (v2/v3), yarn.lock (classic/berry), pnpm-lock.yaml, poetry.lock, Pipfile.lock, Cargo.lock, go.sum, Gemfile.lock
1431
+ - **7 Manifest Parsers:** package.json, requirements.txt, pyproject.toml, go.mod, Gemfile, pom.xml, build.gradle
1432
+ - **CLI Fallbacks:** npm ls, pnpm list, cargo metadata, go list, mvn dependency:tree
1433
+ - **OSV.dev Integration:** Real-time vulnerability database with 24-hour local cache
1434
+ - **Baseline Comparison:** Track dependency drift with save/compare workflow
1435
+ - **HTML Reports:** Visual dashboard with severity charts for compliance
1436
+ - **86 New Tests:** Comprehensive coverage across all SBOM features
1437
+
1438
+ ---
1439
+
1243
1440
  ### v4.0.0 (2026-03-21) - LLM-Powered Code Review Agent
1244
1441
 
1245
1442
  **🚀 Major Release: LLM-Powered Semantic Code Review**
package/index.js CHANGED
@@ -28,6 +28,11 @@ import { runInitHooks } from './src/cli/init-hooks.js';
28
28
  import { runReport } from './src/cli/report.js';
29
29
  import { scoreAivssSchema, scoreAivssTool } from './src/tools/score-aivss.js';
30
30
  import { complianceControlsSchema, getComplianceControls } from './src/tools/compliance-controls.js';
31
+ import { sbomGenerateSchema, sbomGenerate } from './src/tools/sbom-generate.js';
32
+ import { sbomVulnerabilitiesSchema, sbomScanVulnerabilities } from './src/tools/sbom-vulnerabilities.js';
33
+ import { sbomHallucinationsSchema, sbomCheckHallucinations } from './src/tools/sbom-hallucinations.js';
34
+ import { sbomDiffSchema, sbomDiff } from './src/tools/sbom-diff.js';
35
+ import { sbomReportSchema, sbomExportReport } from './src/tools/sbom-report.js';
31
36
 
32
37
  // Handle both ESM and CJS bundling (Smithery bundles to CJS)
33
38
  let __dirname;
@@ -252,6 +257,45 @@ server.tool(
252
257
  getComplianceControls
253
258
  );
254
259
 
260
+ // ===========================================
261
+ // SBOM / SUPPLY CHAIN ANALYSIS
262
+ // ===========================================
263
+
264
+ server.tool(
265
+ "sbom_generate",
266
+ "Generate a CycloneDX v1.5 SBOM for a project. Discovers all dependencies (direct + transitive) from lock files and manifests across Node.js, Python, Go, Rust, Ruby, Java. Use verbosity='minimal' for counts, 'compact' (default) for component list, 'full' for complete CycloneDX JSON.",
267
+ sbomGenerateSchema,
268
+ sbomGenerate
269
+ );
270
+
271
+ server.tool(
272
+ "sbom_scan_vulnerabilities",
273
+ "Cross-reference SBOM components against OSV.dev vulnerability database. Returns CVE IDs, CVSS scores, severity, and fix recommendations. Accepts directory_path (generates fresh) or sbom_path (loads saved artifact).",
274
+ sbomVulnerabilitiesSchema,
275
+ sbomScanVulnerabilities
276
+ );
277
+
278
+ server.tool(
279
+ "sbom_check_hallucinations",
280
+ "Check all packages in an SBOM against official registries to detect hallucinated (AI-invented) package names. Supports npm, pypi, rubygems, dart, perl, raku, crates. Go/Java marked as unsupported.",
281
+ sbomHallucinationsSchema,
282
+ sbomCheckHallucinations
283
+ );
284
+
285
+ server.tool(
286
+ "sbom_diff",
287
+ "Compare current project SBOM against a stored baseline. Reports added, removed, and version-changed packages. Use save_baseline=true to create initial baseline.",
288
+ sbomDiffSchema,
289
+ sbomDiff
290
+ );
291
+
292
+ server.tool(
293
+ "sbom_export_report",
294
+ "Generate an HTML or JSON audit report from SBOM data, optionally enriched with vulnerability scan results. Suitable for PCI-DSS and compliance audits.",
295
+ sbomReportSchema,
296
+ sbomExportReport
297
+ );
298
+
255
299
  // ===========================================
256
300
  // CLI COMMANDS - Extracted to src/cli/
257
301
  // ===========================================
@@ -524,6 +568,95 @@ const cliArgs = process.argv.slice(2);
524
568
  console.error(` Error: ${err.message}\n`);
525
569
  process.exit(1);
526
570
  });
571
+ } else if (cliArgs[0] === 'sbom-generate') {
572
+ const dirPath = cliArgs[1] || '.';
573
+ const verbosityIdx = cliArgs.indexOf('--verbosity');
574
+ const verbosity = verbosityIdx !== -1 ? cliArgs[verbosityIdx + 1] : 'compact';
575
+ const save = cliArgs.includes('--save');
576
+ const outIdx = cliArgs.indexOf('--output');
577
+ const outputPath = outIdx !== -1 ? cliArgs[outIdx + 1] : (save ? join(dirPath, '.scanner', 'sbom.json') : undefined);
578
+
579
+ sbomGenerate({ directory_path: dirPath, output_path: outputPath, verbosity }).then(result => {
580
+ const output = JSON.parse(result.content[0].text);
581
+ console.log(JSON.stringify(output, null, 2));
582
+ process.exit(0);
583
+ }).catch(err => {
584
+ console.error(JSON.stringify({ error: err.message }));
585
+ process.exit(1);
586
+ });
587
+ } else if (cliArgs[0] === 'sbom-vulnerabilities') {
588
+ const dirPath = cliArgs[1] || '.';
589
+ const verbosityIdx = cliArgs.indexOf('--verbosity');
590
+ const verbosity = verbosityIdx !== -1 ? cliArgs[verbosityIdx + 1] : 'compact';
591
+ const sbomIdx = cliArgs.indexOf('--sbom-path');
592
+ const sbomPath = sbomIdx !== -1 ? cliArgs[sbomIdx + 1] : undefined;
593
+
594
+ sbomScanVulnerabilities({
595
+ directory_path: sbomPath ? undefined : dirPath,
596
+ sbom_path: sbomPath,
597
+ verbosity,
598
+ }).then(result => {
599
+ const output = JSON.parse(result.content[0].text);
600
+ console.log(JSON.stringify(output, null, 2));
601
+ process.exit(output.total_vulnerabilities > 0 ? 1 : 0);
602
+ }).catch(err => {
603
+ console.error(JSON.stringify({ error: err.message }));
604
+ process.exit(1);
605
+ });
606
+ } else if (cliArgs[0] === 'sbom-check-hallucinations') {
607
+ const dirPath = cliArgs[1] || '.';
608
+ const verbosityIdx = cliArgs.indexOf('--verbosity');
609
+ const verbosity = verbosityIdx !== -1 ? cliArgs[verbosityIdx + 1] : 'compact';
610
+
611
+ loadPackageLists();
612
+ sbomCheckHallucinations({ directory_path: dirPath, verbosity }).then(result => {
613
+ const output = JSON.parse(result.content[0].text);
614
+ console.log(JSON.stringify(output, null, 2));
615
+ process.exit(output.hallucinated_count > 0 ? 1 : 0);
616
+ }).catch(err => {
617
+ console.error(JSON.stringify({ error: err.message }));
618
+ process.exit(1);
619
+ });
620
+ } else if (cliArgs[0] === 'sbom-diff') {
621
+ const dirPath = cliArgs[1] || '.';
622
+ const verbosityIdx = cliArgs.indexOf('--verbosity');
623
+ const verbosity = verbosityIdx !== -1 ? cliArgs[verbosityIdx + 1] : 'compact';
624
+ const saveBaseline = cliArgs.includes('--save-baseline');
625
+ const baselineIdx = cliArgs.indexOf('--baseline-path');
626
+ const baselinePath = baselineIdx !== -1 ? cliArgs[baselineIdx + 1] : undefined;
627
+
628
+ sbomDiff({ directory_path: dirPath, baseline_path: baselinePath, save_baseline: saveBaseline, verbosity }).then(result => {
629
+ const output = JSON.parse(result.content[0].text);
630
+ console.log(JSON.stringify(output, null, 2));
631
+ process.exit(0);
632
+ }).catch(err => {
633
+ console.error(JSON.stringify({ error: err.message }));
634
+ process.exit(1);
635
+ });
636
+ } else if (cliArgs[0] === 'sbom-report') {
637
+ const dirPath = cliArgs[1] || '.';
638
+ const verbosityIdx = cliArgs.indexOf('--verbosity');
639
+ const verbosity = verbosityIdx !== -1 ? cliArgs[verbosityIdx + 1] : 'compact';
640
+ const formatIdx = cliArgs.indexOf('--format');
641
+ const format = formatIdx !== -1 ? cliArgs[formatIdx + 1] : 'html';
642
+ const outIdx = cliArgs.indexOf('--output');
643
+ const outputPath = outIdx !== -1 ? cliArgs[outIdx + 1] : join(dirPath, '.scanner', 'sbom-report.html');
644
+ const noVulns = cliArgs.includes('--no-vulnerabilities');
645
+
646
+ sbomExportReport({
647
+ directory_path: dirPath,
648
+ format,
649
+ include_vulnerabilities: !noVulns,
650
+ output_path: outputPath,
651
+ verbosity,
652
+ }).then(result => {
653
+ const output = JSON.parse(result.content[0].text);
654
+ console.log(JSON.stringify(output, null, 2));
655
+ process.exit(0);
656
+ }).catch(err => {
657
+ console.error(JSON.stringify({ error: err.message }));
658
+ process.exit(1);
659
+ });
527
660
  } else if (cliArgs[0] === 'scan-clawhub') {
528
661
  // Import and run SAFE ClawHub scanner (no code execution)
529
662
  await import('./src/cli/scan-clawhub-safe.js');
@@ -550,6 +683,12 @@ const cliArgs = process.argv.slice(2);
550
683
  console.log(' scan-action <t> <v> Check agent action before execution');
551
684
  console.log(' audit [--config-path] Audit OpenClaw config for security issues [experimental]');
552
685
  console.log(' harden [--fix] Auto-harden OpenClaw configuration [experimental]\n');
686
+ console.log(' SBOM / Supply Chain:');
687
+ console.log(' sbom-generate <dir> Generate CycloneDX SBOM [--save] [--output <path>]');
688
+ console.log(' sbom-vulnerabilities <dir> Scan SBOM against OSV.dev [--sbom-path <path>]');
689
+ console.log(' sbom-check-hallucinations <dir> Check SBOM packages against registries');
690
+ console.log(' sbom-diff <dir> Compare SBOM against baseline [--save-baseline]');
691
+ console.log(' sbom-report <dir> Generate SBOM audit report [--format html|json]\n');
553
692
  console.log(' (no args) Start MCP server on stdio\n');
554
693
  console.log(' Options:');
555
694
  console.log(' --verbosity <level> minimal|compact|full (default: compact)');
@@ -7,7 +7,7 @@
7
7
  "openclaw": {
8
8
  "extensions": ["tools", "skills"],
9
9
  "emoji": "shield",
10
- "permissions": ["fs:read"]
10
+ "permissions": ["fs:read", "fs:write"]
11
11
  },
12
12
  "tools": [
13
13
  {
@@ -33,6 +33,26 @@
33
33
  {
34
34
  "name": "scanner_health",
35
35
  "description": "Check plugin health status"
36
+ },
37
+ {
38
+ "name": "sbom_generate",
39
+ "description": "Generate CycloneDX SBOM for a project"
40
+ },
41
+ {
42
+ "name": "sbom_scan_vulnerabilities",
43
+ "description": "Scan SBOM against OSV.dev vulnerability database"
44
+ },
45
+ {
46
+ "name": "sbom_check_hallucinations",
47
+ "description": "Check SBOM packages against official registries"
48
+ },
49
+ {
50
+ "name": "sbom_diff",
51
+ "description": "Compare current SBOM against stored baseline"
52
+ },
53
+ {
54
+ "name": "sbom_export_report",
55
+ "description": "Generate HTML/JSON SBOM audit report"
36
56
  }
37
57
  ],
38
58
  "skills": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-security-scanner-mcp",
3
- "version": "4.1.0",
3
+ "version": "4.1.1",
4
4
  "mcpName": "io.github.sinewaveai/agent-security-scanner-mcp",
5
5
  "description": "Security scanner MCP server for AI coding agents. Prompt injection firewall, package hallucination detection (4.3M+ packages), 1700+ vulnerability rules with AST & taint analysis, LLM-powered semantic code review, auto-fix. For Claude Code, Cursor, Windsurf, Cline, OpenClaw.",
6
6
  "main": "index.js",
@@ -0,0 +1,113 @@
1
+ // CycloneDX v1.5 JSON serializer.
2
+ // Consumes the normalized ComponentList model from sbom-component.js.
3
+
4
+ import { randomUUID } from 'crypto';
5
+
6
+ const SPEC_VERSION = '1.5';
7
+ const TOOL_NAME = 'agent-security-scanner-mcp';
8
+
9
+ /**
10
+ * Serialize a ComponentList into a CycloneDX v1.5 BOM.
11
+ * @param {import('./sbom-component.js').ComponentList} componentList
12
+ * @param {object[]} [vulnerabilities=[]]
13
+ * @param {{ toolVersion?: string }} [options={}]
14
+ * @returns {object} CycloneDX JSON object
15
+ */
16
+ export function serialize(componentList, vulnerabilities = [], options = {}) {
17
+ const { toolVersion = '0.0.0' } = options;
18
+ const { components, edges, metadata } = componentList;
19
+
20
+ const bom = {
21
+ bomFormat: 'CycloneDX',
22
+ specVersion: SPEC_VERSION,
23
+ serialNumber: `urn:uuid:${randomUUID()}`,
24
+ version: 1,
25
+ metadata: {
26
+ timestamp: new Date().toISOString(),
27
+ tools: [{
28
+ name: TOOL_NAME,
29
+ version: toolVersion,
30
+ }],
31
+ component: {
32
+ type: 'application',
33
+ name: metadata.name || 'unknown',
34
+ version: metadata.version || '0.0.0',
35
+ 'bom-ref': `pkg:npm/${metadata.name}@${metadata.version}`,
36
+ },
37
+ },
38
+ components: components.map(c => serializeComponent(c)),
39
+ dependencies: serializeDependencies(edges, components),
40
+ };
41
+
42
+ if (vulnerabilities.length > 0) {
43
+ bom.vulnerabilities = vulnerabilities;
44
+ }
45
+
46
+ return bom;
47
+ }
48
+
49
+ function serializeComponent(component) {
50
+ const result = {
51
+ type: 'library',
52
+ name: component.name,
53
+ version: component.version,
54
+ purl: component.purl,
55
+ 'bom-ref': component.purl,
56
+ scope: component.scope === 'optional' ? 'optional' : 'required',
57
+ };
58
+
59
+ if (component.namespace) {
60
+ result.group = component.namespace;
61
+ }
62
+
63
+ const properties = [];
64
+ if (component.isDev) {
65
+ properties.push({ name: 'cdx:development', value: 'true' });
66
+ }
67
+ if (component.ecosystem) {
68
+ properties.push({ name: 'cdx:ecosystem', value: component.ecosystem });
69
+ }
70
+ if (properties.length > 0) {
71
+ result.properties = properties;
72
+ }
73
+
74
+ return result;
75
+ }
76
+
77
+ function serializeDependencies(edges, components) {
78
+ if (!edges || edges.length === 0) {
79
+ // Return flat list — each component depends on nothing
80
+ return components.map(c => ({ ref: c.purl, dependsOn: [] }));
81
+ }
82
+
83
+ // Group edges by source
84
+ const depMap = new Map();
85
+ for (const edge of edges) {
86
+ if (!depMap.has(edge.from)) depMap.set(edge.from, []);
87
+ depMap.get(edge.from).push(edge.to);
88
+ }
89
+
90
+ // Include all components as refs, merge edges
91
+ const allRefs = new Set(components.map(c => c.purl));
92
+ for (const [from, tos] of depMap) {
93
+ allRefs.add(from);
94
+ for (const to of tos) allRefs.add(to);
95
+ }
96
+
97
+ return [...allRefs].map(ref => ({
98
+ ref,
99
+ dependsOn: depMap.get(ref) || [],
100
+ }));
101
+ }
102
+
103
+ /**
104
+ * Add vulnerability entries to an existing BOM.
105
+ * @param {object} bom - CycloneDX BOM object
106
+ * @param {object[]} vulnerabilities - Array of CycloneDX vulnerability objects
107
+ * @returns {object} The mutated BOM
108
+ */
109
+ export function addVulnerabilities(bom, vulnerabilities) {
110
+ if (!bom.vulnerabilities) bom.vulnerabilities = [];
111
+ bom.vulnerabilities.push(...vulnerabilities);
112
+ return bom;
113
+ }