vulnzap-core
Version:
Secure AI-generated code by intercepting vulnerabilities in real-time
466 lines • 20.3 kB
JavaScript
import { Command } from 'commander';
import chalk from 'chalk';
import ora from 'ora';
import inquirer from 'inquirer';
import { startMcpServer, checkVulnerability } from './index.js';
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import os from 'os';
import fs from 'fs';
// Get package version
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf8'));
const version = packageJson.version;
const program = new Command(); // Instantiate Command
async function checkInit() {
const vulnzapLocation = process.cwd() + '/.vulnzap-core';
if (!fs.existsSync(vulnzapLocation)) {
return false;
}
return true;
}
// Ensure .vulnzap folder exists in the user's home directory
const ensureVulnzapFolder = () => {
const vulnzapHomeDir = join(os.homedir(), '.vulnzap');
if (!fs.existsSync(vulnzapHomeDir)) {
fs.mkdirSync(vulnzapHomeDir, { recursive: true });
}
};
ensureVulnzapFolder();
// Banner display
const displayBanner = () => {
console.log(chalk.bold(`
╦ ╦┬ ┬┬ ┌┐┌╔═╗┌─┐┌─┐
╚╗╔╝│ ││ │││╔═╝├─┤├─┘
╚╝ └─┘┴─┘┘└┘╚═╝┴ ┴┴ v${version}
`));
console.log(`${chalk.cyan('Securing AI-Generated Code')}\n`);
};
program
.name('vulnzap-core')
.description('Secure your AI-generated code from vulnerabilities in real-time')
.version(version);
// Command: vulnzap secure (only used by ides to start a connection to the server)
program
.command('secure')
.description('Start the MCP security bridge to protect your AI coding')
.option('--ide <ide-name>', 'Specify IDE integration (cursor, claude-code, windsurf)')
.option('--port <port>', 'Port to use for MCP server', '3456')
/**
* Action handler for the 'secure' command.
* Starts the MCP security bridge.
* If VulnZap is not initialized in the current project, it will
* automatically perform the initialization steps before starting the server.
*/
.action(async (options) => {
try {
// log the present working directory in log file at .cursor folder in home dir
const homedir = os.homedir();
const logFile = join(homedir, '.vulnzap', 'info.log');
const logStream = fs.createWriteStream(logFile, { flags: 'a' });
logStream.write(`VulnZap MCP server initialized by ${options.ide} started in ${process.cwd()} at ${new Date().toISOString()}\n`);
logStream.end();
const checkAlreadyInitialized = await checkInit();
if (!checkAlreadyInitialized) {
// Automatically initialize the project if not already done
const vulnzapLocation = process.cwd() + '/.vulnzap-core';
if (!fs.existsSync(vulnzapLocation)) {
fs.mkdirSync(vulnzapLocation);
}
const scanConfigLocation = vulnzapLocation + '/scans.json';
if (!fs.existsSync(scanConfigLocation)) {
fs.writeFileSync(scanConfigLocation, JSON.stringify({
scans: []
}, null, 2));
}
}
await startMcpServer({
useMcp: options.mcp || true,
ide: options.ide,
port: parseInt(options.port, 10),
});
}
catch (error) {
console.error(chalk.red('Error:'), error.message); // Use console.error for errors
process.exit(1);
}
});
// Command: vulnzap init
program
.command('init')
.description('Initialize VulnZap for your project')
.action(async () => {
displayBanner();
console.log(chalk.yellow('You are on a OSS version. Some features may be unavailable.'));
const spinner = ora('Initializing VulnZap...\n').start();
const checkAlreadyInitialized = await checkInit();
if (checkAlreadyInitialized) {
console.log(chalk.green('✓') + ' VulnZap already initialized');
process.exit(1);
}
try {
const vulnzapLocation = process.cwd() + '/.vulnzap-core';
if (!fs.existsSync(vulnzapLocation)) {
fs.mkdirSync(vulnzapLocation);
}
const scanConfigLocation = vulnzapLocation + '/scans.json';
if (!fs.existsSync(scanConfigLocation)) {
fs.writeFileSync(scanConfigLocation, JSON.stringify({
scans: []
}, null, 2));
}
console.log(chalk.green('✓') + ' VulnZap config file created\n');
spinner.succeed('wohooooo!');
console.log(chalk.yellow('To enable GitHub integration, set the VULNZAP_GITHUB_TOKEN environment variable with your GitHub token'));
console.log(chalk.yellow('To enable National Vulnerability Database(NVD) integration, set the VULNZAP_NVD_API_KEY environment variable with your NVD token'));
console.log(chalk.green('✓') + ' VulnZap initialized successfully');
}
catch (error) {
spinner.fail('Failed to initialize VulnZap');
console.error(chalk.red('Error:'), error.message);
process.exit(1);
}
});
// Command: vulnzap check
program
.command('check <package>')
.description('Check a package for vulnerabilities (format: ecosystem:package-name@version)')
.option('-e, --ecosystem <ecosystem>', 'Package ecosystem (npm, pip)')
.option('-v, --version <version>', 'Package version')
.action(async (packageInput, options) => {
displayBanner();
const checkAlreadyInitialized = await checkInit();
if (!checkAlreadyInitialized) {
console.error(chalk.red('Error: VulnZap is not initialized in this project, run vulnzap init to initialize VulnZap'));
process.exit(1);
}
let packageName, packageVersion, packageEcosystem;
if (!process.env.VULNZAP_GITHUB_TOKEN) {
console.error(chalk.red('Error: VULNZAP_GITHUB_TOKEN not found'));
process.exit(1);
}
if (!process.env.VULNZAP_NVD_API_KEY) {
console.error(chalk.red('Error: VULNZAP_NVD_API_KEY token not found'));
process.exit(1);
}
// Log the event
const logFile = join(os.homedir(), '.vulnzap', 'info.log');
const logStream = fs.createWriteStream(logFile, { flags: 'a' });
logStream.write(`VulnZap check command executed for ${packageInput} at ${new Date().toISOString()}\n`);
logStream.end();
// Parse package input
const packageFormat = /^(npm|pip):([^@]+)@(.+)$/;
const match = packageInput.match(packageFormat);
if (match) {
[, packageEcosystem, packageName, packageVersion] = match;
}
else if (packageInput.includes('@') && !packageInput.startsWith('@')) {
// Fallback for old format package@version
[packageName, packageVersion] = packageInput.split('@');
packageEcosystem = options.ecosystem;
}
else {
packageName = packageInput;
packageVersion = options.version;
packageEcosystem = options.ecosystem;
}
if (!packageVersion) {
console.error(chalk.red('Error: Package version is required'));
console.log('Format: vulnzap check ecosystem:package-name@version');
console.log('Example: vulnzap check npm:express@4.17.1');
console.log('Or: vulnzap check package-name --ecosystem npm --version 4.17.1');
process.exit(1);
}
if (!packageEcosystem) {
console.error(chalk.red('Error: Package ecosystem is required'));
console.log('Format: vulnzap check ecosystem:package-name@version');
console.log('Example: vulnzap check npm:express@4.17.1');
console.log('Or: vulnzap check package-name --ecosystem npm --version 4.17.1');
process.exit(1);
}
const spinner = ora(`Checking ${packageEcosystem}:${packageName}@${packageVersion} for vulnerabilities...`).start();
try {
const result = await checkVulnerability(packageEcosystem, packageName, packageVersion);
spinner.stop();
console.log(chalk.green('✓') + ' Vulnerability scan completed');
if (result.length === 0) {
console.log(chalk.green(`✓ Safe: ${packageName}@${packageVersion} has no known vulnerabilities\n`));
return;
}
for (const vuln of result) {
if (vuln.isVulnerable) {
console.log(chalk.red(`✗ Vulnerable: ${packageName}@${packageVersion} has vulnerabilities\n`));
// Display vulnerability details
vuln.advisories?.forEach(advisory => {
console.log(chalk.yellow(`- ${advisory.title}`));
console.log(` Severity: ${advisory.severity}`);
console.log(` CVE: ${advisory.cve_id || 'N/A'}`);
console.log(` Description: ${advisory.description}`);
console.log('');
});
// Suggest fixed version if available
if (vuln.fixedVersions && vuln.fixedVersions.length > 0) {
console.log(chalk.green('Suggested fix:'));
console.log(`Upgrade to ${vuln.fixedVersions[0]} or later\n`);
}
}
else {
console.log(chalk.green(`✓ Safe: ${packageName}@${packageVersion} has no known vulnerabilities\n`));
}
}
const pwd = process.cwd();
const scanConfigLocation = pwd + '/.vulnzap-core/scans.json';
const scanConfig = JSON.parse(fs.readFileSync(scanConfigLocation, 'utf8'));
const newScan = {
package: `${packageName}@${packageVersion}`,
result: result,
createdAt: new Date().toISOString()
};
scanConfig.scans.push(newScan);
fs.writeFileSync(scanConfigLocation, JSON.stringify(scanConfig, null, 2));
console.log(chalk.green('✓') + ' Vulnerability scan saved to ' + scanConfigLocation);
process.exit(0);
}
catch (error) {
spinner.fail('Vulnerability check failed');
console.error(chalk.red('Error:'), error.message);
process.exit(1);
}
});
// Command: vulnzap connect
program
.command('connect')
.description('Connect VulnZap to your AI-powered IDE')
.option('--ide <ide-name>', 'IDE to connect with (cursor, claude-code, windsurf)')
.action(async (options) => {
// Prompt for IDE if not provided
if (!options.ide) {
console.error(chalk.red('Error: You must specify an IDE to connect with.'));
console.log('Example: vulnzap connect --ide <ide-name>');
process.exit(1);
}
// Log the event
const logFile = join(os.homedir(), '.vulnzap', 'info.log');
const logStream = fs.createWriteStream(logFile, { flags: 'a' });
logStream.write(`VulnZap connect command executed for ${options.ide} at ${new Date().toISOString()}\n`);
logStream.end();
if (options.ide === 'cursor') {
const cursorMcpConfigLocation = os.homedir() + '/.cursor/mcp.json';
if (!fs.existsSync(cursorMcpConfigLocation)) {
console.error(chalk.red('Error: Cursor MCP config not found.'));
console.log('Please install Cursor and try again.');
process.exit(1);
}
const cursorMcpConfig = JSON.parse(fs.readFileSync(cursorMcpConfigLocation, 'utf8'));
// Display info about API keys and ask if user has both
console.log(chalk.cyan('To use the connect command, you need both a GitHub token and an NVD API key.'));
console.log(chalk.yellow('GitHub token: https://github.com/settings/tokens'));
console.log(chalk.yellow('NVD API key: https://nvd.nist.gov/developers/request-an-api-key'));
console.log('\nBoth keys are required for full functionality.');
const { hasKeys } = await inquirer.prompt([
{
type: 'confirm',
name: 'hasKeys',
message: 'Do you have both the GitHub token and NVD API key?',
default: false,
},
]);
if (!hasKeys) {
console.log(chalk.red('Please obtain both API keys before proceeding.'));
process.exit(1);
}
// Prompt for GitHub and NVD API keys
const answers = await inquirer.prompt([
{
type: 'input',
name: 'githubToken',
message: 'Enter your GitHub token:',
},
{
type: 'input',
name: 'nvdApiKey',
message: 'Enter your NVD API key:',
},
]);
let missing = false;
if (!answers.githubToken) {
console.log(chalk.yellow('You can generate a GitHub token at: https://github.com/settings/tokens'));
missing = true;
}
if (!answers.nvdApiKey) {
console.log(chalk.yellow('You can request an NVD API key at: https://nvd.nist.gov/developers/request-an-api-key'));
missing = true;
}
if (missing) {
console.error(chalk.red('Error: Both API keys are required to proceed.'));
process.exit(1);
}
// Save tokens in mcp.json
if (!cursorMcpConfig.mcp) {
cursorMcpConfig.mcpServers = {
VulnZap: {
command: "vulnzap",
args: ["secure", "--ide", "cursor", "--port", "3456"],
env: {
VULNZAP_GITHUB_TOKEN: answers.githubToken,
VULNZAP_NVD_API_KEY: answers.nvdApiKey
}
}
};
}
else {
cursorMcpConfig.mcpServers.VulnZap = {
command: "vulnzap",
args: ["secure", "--ide", "cursor", "--port", "3456"],
env: {
VULNZAP_GITHUB_TOKEN: answers.githubToken,
VULNZAP_NVD_API_KEY: answers.nvdApiKey
}
};
}
fs.writeFileSync(cursorMcpConfigLocation, JSON.stringify(cursorMcpConfig, null, 2));
console.log(chalk.green('✓') + ' Cursor MCP config updated successfully with API keys');
process.exit(0);
}
else {
console.error(chalk.red('Error: Unsupported IDE.'));
console.log('Please use Cursor for now.');
process.exit(1);
}
});
// Command: vulnzap batch
program
.command('batch')
.description('Batch scan dependencies from package.json and requirements.txt')
.option('-o, --output <file>', 'Output file for results (default: vulnzap-results.json)')
.action(async (options) => {
displayBanner();
const checkAlreadyInitialized = await checkInit();
if (!checkAlreadyInitialized) {
console.error(chalk.red('Error: VulnZap is not initialized in this project, run vulnzap init to initialize VulnZap'));
process.exit(1);
}
// Log the event
const logFile = join(os.homedir(), '.vulnzap', 'info.log');
const logStream = fs.createWriteStream(logFile, { flags: 'a' });
logStream.write(`VulnZap batch scan command executed in ${process.cwd()} with output file ${options.output} at ${new Date().toISOString()}\n`);
logStream.end();
const spinner = ora('Initiating batch vulnerability scan...').start();
const results = [];
let totalPackages = 0;
let vulnerablePackages = 0;
let totalVulnerabilities = 0;
try {
// Check for package.json
const packageJsonPath = join(process.cwd(), 'package.json');
if (fs.existsSync(packageJsonPath)) {
spinner.text = 'Scanning npm dependencies...';
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
const dependencies = {
...packageJson.dependencies || {},
...packageJson.devDependencies || {}
};
for (const [name, version] of Object.entries(dependencies)) {
const cleanVersion = version.replace(/[\^~]/g, '');
spinner.text = `Scanning ${name}@${cleanVersion}...`;
try {
const vulnResult = await checkVulnerability('npm', name, cleanVersion);
totalPackages++;
if (vulnResult.length > 0 && vulnResult.some(r => r.isVulnerable)) {
vulnerablePackages++;
totalVulnerabilities += vulnResult.reduce((acc, r) => acc + (r.advisories?.length || 0), 0);
}
results.push({
ecosystem: 'npm',
package: name,
version: cleanVersion,
result: vulnResult
});
}
catch (error) {
console.error(chalk.yellow(`Warning: Failed to scan ${name}@${cleanVersion}`));
}
}
}
// Check for requirements.txt
const requirementsPath = join(process.cwd(), 'requirements.txt');
if (fs.existsSync(requirementsPath)) {
spinner.text = 'Scanning Python dependencies...';
const requirements = fs.readFileSync(requirementsPath, 'utf8')
.split('\n')
.filter(line => line && !line.startsWith('#'))
.map(line => {
const [name, version] = line.split('==');
return { name: name.trim(), version: version ? version.trim() : 'latest' };
});
for (const pkg of requirements) {
spinner.text = `Scanning ${pkg.name}@${pkg.version}...`;
try {
const vulnResult = await checkVulnerability('pip', pkg.name, pkg.version);
totalPackages++;
if (vulnResult.length > 0 && vulnResult.some(r => r.isVulnerable)) {
vulnerablePackages++;
totalVulnerabilities += vulnResult.reduce((acc, r) => acc + (r.advisories?.length || 0), 0);
}
results.push({
ecosystem: 'pip',
package: pkg.name,
version: pkg.version,
result: vulnResult
});
}
catch (error) {
console.error(chalk.yellow(`Warning: Failed to scan ${pkg.name}@${pkg.version}`));
}
}
}
// Save results
const outputFile = options.output || 'vulnzap-results.json';
fs.writeFileSync(outputFile, JSON.stringify(results, null, 2));
spinner.succeed('Batch scan completed');
console.log('\nScan summary:');
console.log(chalk.green('✓') + ` Packages scanned: ${totalPackages}`);
console.log(chalk.red('✗') + ` Vulnerabilities found: ${totalVulnerabilities}`);
console.log(chalk.yellow('!') + ` Packages with known issues: ${vulnerablePackages}`);
console.log('\nDetailed results saved to:', outputFile);
// Save to vulnzap scans history
const scanConfigLocation = join(process.cwd(), '.vulnzap-core/scans.json');
const scanConfig = JSON.parse(fs.readFileSync(scanConfigLocation, 'utf8'));
scanConfig.scans.push({
type: 'batch',
results,
summary: {
totalPackages,
vulnerablePackages,
totalVulnerabilities
},
createdAt: new Date().toISOString()
});
fs.writeFileSync(scanConfigLocation, JSON.stringify(scanConfig, null, 2));
process.exit(0);
}
catch (error) {
spinner.fail('Batch scan failed');
console.error(chalk.red('Error:'), error.message);
process.exit(1);
}
});
// Command: vulnzap help
program
.command('help')
.description('Display help information')
.action(() => {
displayBanner();
program.help();
});
// Parse arguments
program.parse(process.argv);
// If no args, display help
if (process.argv.length === 2) {
displayBanner();
program.help();
}
//# sourceMappingURL=cli.js.map