feat(scanner): add AI-BOM generator — CycloneDX 1.6 format for AI supply chain transparency
New bom-builder.mjs discovers AI components (models, MCP servers, plugins, knowledge files, hooks) and builds CycloneDX 1.6 JSON BOMs. CLI entry point: node scanners/ai-bom-generator.mjs <target>. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
269c14445c
commit
0439e0f650
3 changed files with 383 additions and 0 deletions
65
plugins/llm-security/scanners/ai-bom-generator.mjs
Normal file
65
plugins/llm-security/scanners/ai-bom-generator.mjs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/env node
|
||||
// ai-bom-generator.mjs — AI Bill of Materials generator
|
||||
// Discovers AI components (models, MCP servers, plugins, knowledge, hooks)
|
||||
// and outputs a CycloneDX 1.6-compatible JSON BOM.
|
||||
//
|
||||
// CLI: node scanners/ai-bom-generator.mjs <target> [--output-file <path>]
|
||||
// Scanner prefix: BOM. Always exits 0 (informational).
|
||||
// Zero external dependencies.
|
||||
|
||||
import { resolve } from 'node:path';
|
||||
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { discoverComponents, buildAIBOM } from './lib/bom-builder.mjs';
|
||||
|
||||
function parseArgs(argv) {
|
||||
const args = { target: null, outputFile: null };
|
||||
for (let i = 2; i < argv.length; i++) {
|
||||
if (argv[i] === '--output-file' && argv[i + 1]) {
|
||||
args.outputFile = argv[++i];
|
||||
} else if (!args.target) {
|
||||
args.target = argv[i];
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = parseArgs(process.argv);
|
||||
if (!args.target) {
|
||||
process.stderr.write('Usage: node ai-bom-generator.mjs <target-path> [--output-file <path>]\n');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const targetPath = resolve(args.target);
|
||||
if (!existsSync(targetPath)) {
|
||||
process.stderr.write(`Target path does not exist: ${targetPath}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Read project metadata from package.json if available
|
||||
let projectMeta = {};
|
||||
try {
|
||||
const pkg = JSON.parse(readFileSync(resolve(targetPath, 'package.json'), 'utf-8'));
|
||||
projectMeta = { name: pkg.name, version: pkg.version };
|
||||
} catch { /* no package.json */ }
|
||||
|
||||
const components = await discoverComponents(targetPath);
|
||||
const bom = buildAIBOM(components, projectMeta);
|
||||
|
||||
const jsonStr = JSON.stringify(bom, null, 2) + '\n';
|
||||
if (args.outputFile) {
|
||||
writeFileSync(args.outputFile, jsonStr);
|
||||
process.stderr.write(`[ai-bom] BOM written to ${args.outputFile} (${components.length} components)\n`);
|
||||
} else {
|
||||
process.stdout.write(jsonStr);
|
||||
}
|
||||
|
||||
process.stderr.write(`[ai-bom] Discovered ${components.length} AI components\n`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
process.stderr.write(`Fatal error: ${err.message}\n`);
|
||||
process.exit(1);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue