endpoint-sentinel
Version:
User-friendly security scanner with interactive setup that scales from beginner to expert
363 lines ⢠14.4 kB
JavaScript
;
/**
* Semantic CLI Commands
* User-friendly commands with progressive setup
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SemanticCommands = void 0;
const chalk_1 = __importDefault(require("chalk"));
const config_manager_js_1 = require("../utils/config-manager.js");
const interactive_setup_js_1 = require("../utils/interactive-setup.js");
const scanner_js_1 = require("../core/scanner.js");
const logger_js_1 = require("../utils/logger.js");
const output_js_1 = require("./output.js");
class SemanticCommands {
configManager;
interactiveSetup;
constructor() {
this.configManager = new config_manager_js_1.ConfigManager();
this.interactiveSetup = new interactive_setup_js_1.InteractiveSetup();
}
/**
* Register all semantic commands
*/
registerCommands(program) {
this.registerScanCommand(program);
this.registerQuickCommand(program);
this.registerConfigCommands(program);
this.registerListCommand(program);
}
/**
* Main scan command with progressive setup
*/
registerScanCommand(program) {
program
.command('scan <url>')
.description('šÆ Scan a URL with automatic setup')
.option('-f, --fresh', 'Force fresh setup (ignore existing config)')
.option('-t, --token <token>', 'Quick auth token override')
.option('-o, --output <file>', 'Output file for results')
.option('--format <format>', 'Output format (json, csv, console)', 'console')
.option('-v, --verbose', 'Verbose output')
.option('-r, --rate-limit <number>', 'Requests per second override', parseFloat)
.option('--non-interactive', 'Skip interactive prompts (for CI/CD)')
.action(async (url, options) => {
await this.handleSemanticScan(url, options);
});
}
/**
* Quick command for power users
*/
registerQuickCommand(program) {
program
.command('quick <url>')
.description('ā” Quick scan with smart defaults')
.option('-t, --token <token>', 'Authentication token')
.option('-k, --keywords <keywords>', 'Comma-separated keywords')
.option('-o, --output <file>', 'Output file')
.option('--format <format>', 'Output format', 'console')
.action(async (url, options) => {
await this.handleQuickScan(url, options);
});
}
/**
* Configuration management commands
*/
registerConfigCommands(program) {
const configCmd = program
.command('config')
.description('š§ Manage configurations');
configCmd
.command('list')
.description('š List saved configurations')
.action(async () => {
await this.handleConfigList();
});
configCmd
.command('show <url>')
.description('šļø Show configuration for URL')
.action(async (url) => {
await this.handleConfigShow(url);
});
configCmd
.command('edit <url>')
.description('āļø Edit configuration for URL')
.action(async (url) => {
await this.handleConfigEdit(url);
});
configCmd
.command('delete <url>')
.description('šļø Delete configuration for URL')
.action(async (url) => {
await this.handleConfigDelete(url);
});
configCmd
.command('reset')
.description('š Reset all configurations')
.option('--confirm', 'Skip confirmation prompt')
.action(async (options) => {
await this.handleConfigReset(options);
});
}
/**
* List command for saved scans
*/
registerListCommand(program) {
program
.command('list')
.alias('ls')
.description('š List saved configurations and recent scans')
.action(async () => {
await this.handleList();
});
}
/**
* Handle semantic scan command
*/
async handleSemanticScan(url, options) {
try {
(0, output_js_1.displayBanner)();
let config;
let useExistingConfig = false;
// Check for existing config (unless --fresh is specified)
if (!options.fresh && this.configManager.hasConfig(url)) {
if (options.nonInteractive) {
useExistingConfig = true;
}
else {
useExistingConfig = await this.interactiveSetup.shouldUseExistingConfig(url);
}
}
if (useExistingConfig) {
// Use existing config with any CLI overrides
config = this.configManager.buildScanConfig(url, this.extractOverrides(options));
console.log(chalk_1.default.green('ā
Using saved configuration'));
}
else {
if (options.nonInteractive) {
// Non-interactive mode: use quick setup or fail
if (options.token) {
await this.interactiveSetup.quickSetup(url, options.token);
config = this.configManager.buildScanConfig(url, this.extractOverrides(options));
}
else {
config = this.buildBasicConfig(url, options);
}
}
else {
// Interactive setup
await this.interactiveSetup.setupDomain(url);
config = this.configManager.buildScanConfig(url, this.extractOverrides(options));
}
}
// Execute the scan
await this.executeScan(config);
}
catch (error) {
console.error(chalk_1.default.red('ā Scan failed:'), error instanceof Error ? error.message : 'Unknown error');
process.exit(1);
}
}
/**
* Handle quick scan command
*/
async handleQuickScan(url, options) {
try {
(0, output_js_1.displayBanner)();
console.log(chalk_1.default.blue.bold('\nā” Quick Scan Mode\n'));
// Build config with smart defaults
const config = this.buildQuickConfig(url, options);
// Auto-save if token provided
if (options.token) {
await this.interactiveSetup.quickSetup(url, options.token);
}
await this.executeScan(config);
}
catch (error) {
console.error(chalk_1.default.red('ā Quick scan failed:'), error instanceof Error ? error.message : 'Unknown error');
process.exit(1);
}
}
/**
* Execute scan with config
*/
async executeScan(config) {
console.log(chalk_1.default.blue('šÆ Target:'), chalk_1.default.white(config.target));
console.log(chalk_1.default.blue('ā” Rate:'), chalk_1.default.white(`${config.rateLimit || 2} req/s`));
if (config.keywords && config.keywords.length > 0) {
console.log(chalk_1.default.blue('š Keywords:'), chalk_1.default.white(config.keywords.join(', ')));
}
// Initialize scanner
const logger = (0, logger_js_1.createLogger)({
level: config.verbose ? 'debug' : 'info',
auditEnabled: true
});
const scanner = new scanner_js_1.EndpointSentinel(logger);
const progressDisplay = (0, output_js_1.displayProgress)();
try {
const results = await scanner.scan(config);
progressDisplay.stop();
await (0, output_js_1.displayResults)(results, config.output ? 'json' : 'console', config.output);
const criticalFindings = results.findings.filter((f) => f.severity === 'critical').length;
if (criticalFindings > 0) {
console.log(chalk_1.default.red(`\nā ļø Found ${criticalFindings} critical vulnerabilities!`));
}
process.exit(criticalFindings > 0 ? 2 : 0);
}
catch (scanError) {
progressDisplay.stop();
throw scanError;
}
}
/**
* Extract CLI overrides from options
*/
extractOverrides(options) {
const overrides = {};
if (options.token) {
overrides.cookie = `token=${options.token}`;
}
if (options.output)
overrides.output = options.output;
if (options.verbose)
overrides.verbose = options.verbose;
if (options.rateLimit)
overrides.rateLimit = options.rateLimit;
return overrides;
}
/**
* Build basic config for non-interactive mode
*/
buildBasicConfig(url, options) {
const config = {
target: url,
consent: true,
rateLimit: options.rateLimit || 2
};
if (options.token) {
config.cookie = `token=${options.token}`;
}
if (options.output)
config.output = options.output;
if (options.verbose)
config.verbose = options.verbose;
return config;
}
/**
* Build quick config with smart defaults
*/
buildQuickConfig(url, options) {
const config = {
target: url,
consent: true,
rateLimit: 5, // Faster for quick scans
keywords: ['api', 'admin', 'user'] // Smart defaults
};
if (options.token) {
config.cookie = `token=${options.token}`;
}
if (options.keywords) {
config.keywords = options.keywords.split(',').map((k) => k.trim());
}
if (options.output)
config.output = options.output;
return config;
}
/**
* Configuration management handlers
*/
async handleConfigList() {
const configs = this.configManager.listConfigs();
if (configs.length === 0) {
console.log(chalk_1.default.yellow('š No saved configurations found'));
console.log(chalk_1.default.gray('Run a scan to create your first configuration!'));
return;
}
console.log(chalk_1.default.blue.bold('\nš Saved Configurations\n'));
configs.forEach((config, index) => {
console.log(chalk_1.default.white(`${index + 1}. ${config.name || config.domain}`));
console.log(chalk_1.default.gray(` š ${config.domain}`));
console.log(chalk_1.default.gray(` š ${this.getAuthDisplayName(config.authType)}`));
if (config.defaultKeywords) {
console.log(chalk_1.default.gray(` š ${config.defaultKeywords.join(', ')}`));
}
console.log(chalk_1.default.gray(` š
${config.updatedAt.toLocaleDateString()}\n`));
});
}
async handleConfigShow(url) {
const config = this.configManager.getConfig(url);
if (!config) {
console.log(chalk_1.default.red(`ā No configuration found for ${url}`));
return;
}
console.log(chalk_1.default.blue.bold(`\nš Configuration for ${config.domain}\n`));
console.log(chalk_1.default.white('Name:'), config.name || 'Unnamed');
console.log(chalk_1.default.white('Domain:'), config.domain);
console.log(chalk_1.default.white('Auth Type:'), this.getAuthDisplayName(config.authType));
if (config.defaultKeywords) {
console.log(chalk_1.default.white('Keywords:'), config.defaultKeywords.join(', '));
}
console.log(chalk_1.default.white('Rate Limit:'), `${config.rateLimit || 2} req/s`);
console.log(chalk_1.default.white('Created:'), config.createdAt.toLocaleString());
console.log(chalk_1.default.white('Updated:'), config.updatedAt.toLocaleString());
if (config.notes) {
console.log(chalk_1.default.white('Notes:'), config.notes);
}
}
async handleConfigEdit(url) {
if (!this.configManager.hasConfig(url)) {
console.log(chalk_1.default.red(`ā No configuration found for ${url}`));
console.log(chalk_1.default.gray('Run a scan first to create a configuration'));
return;
}
console.log(chalk_1.default.blue('āļø Editing configuration - running setup again...'));
await this.interactiveSetup.setupDomain(url);
}
async handleConfigDelete(url) {
if (!this.configManager.hasConfig(url)) {
console.log(chalk_1.default.red(`ā No configuration found for ${url}`));
return;
}
const domain = new URL(url).hostname;
const deleted = this.configManager.deleteConfig(url);
if (deleted) {
console.log(chalk_1.default.green(`ā
Configuration deleted for ${domain}`));
}
else {
console.log(chalk_1.default.red(`ā Failed to delete configuration for ${domain}`));
}
}
async handleConfigReset(options) {
if (!options.confirm) {
console.log(chalk_1.default.yellow('ā ļø This will delete ALL saved configurations!'));
console.log(chalk_1.default.gray('Use --confirm flag to proceed'));
return;
}
this.configManager.reset();
console.log(chalk_1.default.green('ā
All configurations reset'));
}
async handleList() {
await this.handleConfigList();
}
/**
* Get display name for auth type
*/
getAuthDisplayName(authType) {
const names = {
'none': 'No Authentication',
'session_cookie': 'Session Cookie',
'jwt': 'JWT Token',
'bearer_token': 'Bearer Token',
'api_key': 'API Key',
'basic': 'Basic Auth',
'oauth2': 'OAuth2',
'custom': 'Custom'
};
return names[authType] || authType;
}
}
exports.SemanticCommands = SemanticCommands;
//# sourceMappingURL=semantic-commands.js.map