Pre-installation verification of VS Code extensions via URL — fetch a remote VSIX, extract it in a hardened sandbox, and run the existing IDE scanner pipeline against it. No npm dependencies. Sources: - VS Code Marketplace (publisher.gallery.vsassets.io direct download) - OpenVSX (open-vsx.org official API) - Direct .vsix HTTPS URLs Defenses: - HTTPS-only, TLS verified, manual redirect with per-source host whitelist - 30s total timeout via AbortController - 50MB compressed cap, 500MB uncompressed, 100x expansion ratio - Zero-dep ZIP extractor: zip-slip, absolute paths, drive letters, NUL bytes, symlinks (Unix mode 0xA000), depth limits, ZIP64 rejected, encrypted rejected - SHA-256 streamed during fetch, surfaced in meta.source - Temp dir cleanup in all paths (try/finally) Files: - scanners/lib/vsix-fetch.mjs (HTTPS fetcher, host whitelist, streaming SHA-256) - scanners/lib/zip-extract.mjs (zero-dep parser with hardening caps) - knowledge/marketplace-api-notes.md (endpoint reference) - 3 test files (48 tests added: vsix-fetch, zip-extract, ide-extension-url) Tests: 1296 → 1344 (all green). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
85 lines
2.9 KiB
JavaScript
Executable file
85 lines
2.9 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
// llm-security CLI — standalone entry point for llm-security scanners.
|
|
// Usage: llm-security <subcommand> [args]
|
|
// Works without Claude Code. Zero dependencies.
|
|
|
|
import { spawn } from 'node:child_process';
|
|
import { readFileSync } from 'node:fs';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { dirname, resolve } from 'node:path';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const ROOT = resolve(__dirname, '..');
|
|
const PKG = JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf8'));
|
|
|
|
const USAGE = `llm-security v${PKG.version} — AI security scanning for Claude Code projects
|
|
|
|
Usage: llm-security <command> [options]
|
|
|
|
Commands:
|
|
scan <target> [--fail-on <critical|high|medium|low>] [--compact]
|
|
[--format sarif] [--output-file <path>] [--baseline] [--save-baseline]
|
|
Run deterministic deep-scan (10 scanners)
|
|
deep-scan <target>
|
|
Alias for scan
|
|
posture <target>
|
|
Quick security posture assessment (16 categories)
|
|
audit-bom <target> [--output-file <path>]
|
|
Generate AI Bill of Materials (CycloneDX 1.6)
|
|
ide-scan [target|url] [--vscode-only] [--intellij-only] [--include-builtin]
|
|
[--online] [--format compact|json] [--fail-on <severity>]
|
|
Scan installed VS Code / JetBrains extensions, OR fetch a remote VSIX:
|
|
- https://marketplace.visualstudio.com/items?itemName=publisher.name
|
|
- https://open-vsx.org/extension/publisher/name[/version]
|
|
- https://example.com/foo.vsix (direct .vsix download)
|
|
benchmark [--adaptive] [--category <name>]
|
|
Run attack simulation benchmark
|
|
|
|
Options:
|
|
--help Show this help
|
|
--version Show version
|
|
`;
|
|
|
|
const [subcommand, ...rest] = process.argv.slice(2);
|
|
|
|
if (!subcommand || subcommand === '--help' || subcommand === '-h') {
|
|
process.stdout.write(USAGE);
|
|
process.exit(0);
|
|
}
|
|
|
|
if (subcommand === '--version' || subcommand === '-v') {
|
|
process.stdout.write(`${PKG.version}\n`);
|
|
process.exit(0);
|
|
}
|
|
|
|
// Map subcommands to scanner scripts and their arguments
|
|
const COMMANDS = {
|
|
scan: { script: 'scanners/scan-orchestrator.mjs' },
|
|
'deep-scan': { script: 'scanners/scan-orchestrator.mjs' },
|
|
posture: { script: 'scanners/posture-scanner.mjs' },
|
|
'audit-bom': { script: 'scanners/ai-bom-generator.mjs' },
|
|
'ide-scan': { script: 'scanners/ide-extension-scanner.mjs' },
|
|
benchmark: { script: 'scanners/attack-simulator.mjs', prependArgs: ['--benchmark', '--json'] },
|
|
};
|
|
|
|
const cmd = COMMANDS[subcommand];
|
|
if (!cmd) {
|
|
process.stderr.write(`Unknown command: ${subcommand}\n\n`);
|
|
process.stderr.write(USAGE);
|
|
process.exit(1);
|
|
}
|
|
|
|
const scriptPath = resolve(ROOT, cmd.script);
|
|
const args = [...(cmd.prependArgs || []), ...rest];
|
|
|
|
const child = spawn('node', [scriptPath, ...args], {
|
|
cwd: process.cwd(),
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
});
|
|
|
|
child.stdout.pipe(process.stdout);
|
|
child.stderr.pipe(process.stderr);
|
|
|
|
child.on('close', (code) => {
|
|
process.exitCode = code ?? 1;
|
|
});
|