UNPKG

endpoint-sentinel

Version:

User-friendly security scanner with interactive setup that scales from beginner to expert

305 lines • 15.7 kB
#!/usr/bin/env node "use strict"; /** * Endpoint Sentinel CLI Entry Point * Production-ready command line interface with comprehensive validation * Now featuring user-friendly semantic commands with progressive setup */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const commander_1 = require("commander"); const chalk_1 = __importDefault(require("chalk")); const scanner_js_1 = require("../core/scanner.js"); const consent_js_1 = require("../utils/consent.js"); const validation_js_1 = require("../utils/validation.js"); const logger_js_1 = require("../utils/logger.js"); const output_js_1 = require("./output.js"); const semantic_commands_js_1 = require("./semantic-commands.js"); const program = new commander_1.Command(); const semanticCommands = new semantic_commands_js_1.SemanticCommands(); async function main() { try { program .name('endpoint-sentinel') .description('šŸ›”ļø Intelligent CLI-based web security scanner for modern applications') .version('1.0.0') .helpOption('-h, --help', 'Display help information') .configureHelp({ sortSubcommands: true, subcommandTerm: (cmd) => cmd.name() }); // Register new semantic commands (user-friendly) semanticCommands.registerCommands(program); // Legacy commands (maintain backward compatibility) program .command('legacy:scan') .description('šŸ” [Legacy] Scan with traditional flags') .requiredOption('-t, --target <url>', 'Target URL to scan (e.g., https://example.com)') .option('-k, --keywords <keywords>', 'Comma-separated keywords for endpoint discovery') .option('-c, --cookie <cookie>', 'Authentication cookie string for authenticated scanning') .option('-o, --output <file>', 'Output file path for scan results (JSON/CSV)') .option('--consent', 'Explicit consent flag - REQUIRED for all scans') .option('-r, --rate-limit <number>', 'Requests per second (default: 2)', parseFloat) .option('--timeout <number>', 'Request timeout in milliseconds (default: 30000)', parseInt) .option('--user-agent <string>', 'Custom User-Agent string') .option('--max-redirects <number>', 'Maximum number of redirects to follow (default: 5)', parseInt) .option('--concurrent <number>', 'Number of concurrent requests (default: 5)', parseInt) .option('-v, --verbose', 'Enable verbose output for debugging') .option('--format <format>', 'Output format: json, csv, or console (default: console)', 'console') .action(async (options) => { await handleLegacyScanCommand(options); }); program .command('validate') .description('āœ… Validate target URL and configuration without scanning') .requiredOption('-t, --target <url>', 'Target URL to validate') .option('-c, --cookie <cookie>', 'Cookie string to validate') .action(async (options) => { await handleValidateCommand(options); }); program .command('consent') .description('šŸ“‹ Display consent information and legal requirements') .action(async () => { await handleConsentCommand(); }); program .command('examples') .description('šŸ“š Show usage examples and best practices') .action(async () => { displayExamples(); }); // Show enhanced banner and help for main command if (process.argv.length === 2) { displayEnhancedHelp(); process.exit(0); } await program.parseAsync(process.argv); } catch (error) { console.error(chalk_1.default.red('āŒ Critical Error:'), error instanceof Error ? error.message : 'Unknown error'); process.exit(1); } } function displayEnhancedHelp() { (0, output_js_1.displayBanner)(); console.log(chalk_1.default.blue.bold('šŸš€ Quick Start Commands:\n')); console.log(chalk_1.default.white('es scan <url>') + chalk_1.default.gray(' # Interactive setup for new domains')); console.log(chalk_1.default.white('es quick <url> -t <token>') + chalk_1.default.gray(' # Quick scan with token')); console.log(chalk_1.default.white('es list') + chalk_1.default.gray(' # Show saved configurations')); console.log(chalk_1.default.white('es config list') + chalk_1.default.gray(' # Manage configurations\n')); console.log(chalk_1.default.blue.bold('šŸŽÆ Examples:\n')); console.log(chalk_1.default.gray('# First-time scanning (interactive setup)')); console.log(chalk_1.default.white('es scan https://example.com\n')); console.log(chalk_1.default.gray('# Quick scan with your JWT token')); console.log(chalk_1.default.white('es quick https://example.com -t your-jwt-token-here\n')); console.log(chalk_1.default.gray('# Use saved configuration')); console.log(chalk_1.default.white('es scan https://example.com # Will ask to use existing config\n')); console.log(chalk_1.default.gray('# Fresh setup (ignore saved config)')); console.log(chalk_1.default.white('es scan https://example.com --fresh\n')); console.log(chalk_1.default.blue.bold('šŸ“š For more help:\n')); console.log(chalk_1.default.white('es --help') + chalk_1.default.gray(' # Full command reference')); console.log(chalk_1.default.white('es examples') + chalk_1.default.gray(' # Detailed examples')); console.log(chalk_1.default.white('es consent') + chalk_1.default.gray(' # Legal information\n')); } async function handleLegacyScanCommand(options) { try { // Validate required consent if (!options.consent) { console.error(chalk_1.default.red('āŒ Consent Required')); console.log(chalk_1.default.yellow('You must provide explicit consent using --consent flag')); console.log(chalk_1.default.gray('Run: endpoint-sentinel consent - to review terms')); process.exit(1); } // Validate and parse configuration const config = await parseAndValidateConfig(options); // Display banner and scan information (0, output_js_1.displayBanner)(); console.log(chalk_1.default.blue('šŸŽÆ Target:'), chalk_1.default.white(config.target)); console.log(chalk_1.default.blue('⚔ Rate Limit:'), 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 logger const logger = (0, logger_js_1.createLogger)({ level: config.verbose ? 'debug' : 'info', auditEnabled: true }); // Create scanner instance const scanner = new scanner_js_1.EndpointSentinel(logger); // Setup progress display const progressDisplay = (0, output_js_1.displayProgress)(); try { // Execute scan const results = await scanner.scan(config); progressDisplay.stop(); // Display results await (0, output_js_1.displayResults)(results, options.format, config.output); // Exit with appropriate code const criticalFindings = results.findings.filter((f) => f.severity === 'critical').length; process.exit(criticalFindings > 0 ? 2 : 0); } catch (scanError) { progressDisplay.stop(); logger.error('Scan execution failed', scanError); throw scanError; } } catch (error) { console.error(chalk_1.default.red('āŒ Scan Failed:'), error instanceof Error ? error.message : 'Unknown error'); process.exit(1); } } async function handleValidateCommand(options) { try { console.log(chalk_1.default.blue('šŸ” Validating configuration...')); // Validate target URL const targetValidation = (0, validation_js_1.validateTarget)(options.target); console.log(targetValidation.isValid ? chalk_1.default.green('āœ… Target URL is valid') : chalk_1.default.red(`āŒ Target URL invalid: ${targetValidation.error}`)); // Validate cookie if provided if (options.cookie) { console.log(chalk_1.default.green('āœ… Cookie string provided')); } // Check network connectivity console.log(chalk_1.default.blue('🌐 Testing network connectivity...')); const logger = (0, logger_js_1.createLogger)({ level: 'error' }); const scanner = new scanner_js_1.EndpointSentinel(logger); try { await scanner.validateConnectivity(options.target); console.log(chalk_1.default.green('āœ… Network connectivity confirmed')); } catch (error) { console.log(chalk_1.default.red(`āŒ Network connectivity failed: ${error instanceof Error ? error.message : 'Unknown error'}`)); } console.log(chalk_1.default.green('\nāœ… Validation complete')); } catch (error) { console.error(chalk_1.default.red('āŒ Validation failed:'), error instanceof Error ? error.message : 'Unknown error'); process.exit(1); } } async function handleConsentCommand() { const consentManager = new consent_js_1.ConsentManager(); console.log(consentManager.getConsentText()); console.log(chalk_1.default.yellow('\nšŸ“ To proceed with scanning, use the --consent flag:')); console.log(chalk_1.default.gray('endpoint-sentinel legacy:scan --target https://example.com --consent')); console.log(chalk_1.default.blue('\nšŸš€ Or use the new semantic commands:')); console.log(chalk_1.default.gray('es scan https://example.com # Interactive setup with consent check')); } function displayExamples() { console.log(chalk_1.default.blue.bold('\nšŸ“š Endpoint Sentinel - Usage Examples\n')); console.log(chalk_1.default.yellow.bold('šŸš€ New Semantic Commands (Recommended):\n')); console.log(chalk_1.default.white('# First-time scanning with interactive setup')); console.log(chalk_1.default.gray('es scan https://example.com\n')); console.log(chalk_1.default.white('# Quick scan with JWT token')); console.log(chalk_1.default.gray('es quick https://example.com -t your-jwt-token-here\n')); console.log(chalk_1.default.white('# Scan with custom token override')); console.log(chalk_1.default.gray('es scan https://example.com -t newtoken123\n')); console.log(chalk_1.default.white('# Fresh setup (ignore saved config)')); console.log(chalk_1.default.gray('es scan https://example.com --fresh\n')); console.log(chalk_1.default.white('# Configuration management')); console.log(chalk_1.default.gray('es config list # List saved configs')); console.log(chalk_1.default.gray('es config show https://app.com # Show specific config')); console.log(chalk_1.default.gray('es config edit https://app.com # Edit configuration')); console.log(chalk_1.default.gray('es config delete https://app.com # Delete configuration\n')); console.log(chalk_1.default.yellow.bold('šŸ› ļø Legacy Commands (Advanced Users):\n')); console.log(chalk_1.default.white('# Traditional command with all flags')); console.log(chalk_1.default.gray('es legacy:scan --target https://example.com --consent\n')); console.log(chalk_1.default.white('# Authenticated scan with keywords')); console.log(chalk_1.default.gray('es legacy:scan --target https://app.example.com --keywords "admin,api,user" --cookie "session=abc123" --consent\n')); console.log(chalk_1.default.white('# High-performance scan')); console.log(chalk_1.default.gray('es legacy:scan --target https://api.example.com --rate-limit 5 --concurrent 10 --consent\n')); console.log(chalk_1.default.blue.bold('šŸŽÆ Best Practices:\n')); console.log(chalk_1.default.gray('• Use semantic commands (es scan) for better experience')); console.log(chalk_1.default.gray('• Always obtain proper authorization before scanning')); console.log(chalk_1.default.gray('• Save configurations for repeated scans')); console.log(chalk_1.default.gray('• Use --fresh flag to reconfigure saved domains')); console.log(chalk_1.default.gray('• Export results with --output for analysis')); } async function parseAndValidateConfig(options) { // Validate target URL const targetValidation = (0, validation_js_1.validateTarget)(options.target); if (!targetValidation.isValid) { throw new Error(`Invalid target URL: ${targetValidation.error}`); } // Validate keywords let keywords; if (options.keywords) { const keywordValidation = (0, validation_js_1.validateKeywords)(options.keywords); if (!keywordValidation.isValid) { throw new Error(`Invalid keywords: ${keywordValidation.error}`); } keywords = keywordValidation.keywords; } // Validate output file if (options.output) { const outputValidation = (0, validation_js_1.validateOutput)(options.output); if (!outputValidation.isValid) { throw new Error(`Invalid output file: ${outputValidation.error}`); } } // Validate numeric options if (options.rateLimit && options.rateLimit <= 0) { throw new Error('Rate limit must be positive'); } if (options.timeout && options.timeout <= 0) { throw new Error('Timeout must be positive'); } if (options.maxRedirects && options.maxRedirects < 0) { throw new Error('Max redirects cannot be negative'); } if (options.concurrent && options.concurrent <= 0) { throw new Error('Concurrent requests must be positive'); } const config = { target: options.target, consent: true }; if (keywords) config.keywords = keywords; if (options.cookie) config.cookie = options.cookie; if (options.output) config.output = options.output; if (options.rateLimit) config.rateLimit = options.rateLimit; if (options.timeout) config.timeout = options.timeout; if (options.userAgent) config.userAgent = options.userAgent; if (options.maxRedirects) config.maxRedirects = options.maxRedirects; if (options.concurrent) config.concurrent = options.concurrent; if (options.verbose) config.verbose = options.verbose; return config; } // Handle uncaught exceptions process.on('uncaughtException', (error) => { console.error(chalk_1.default.red('šŸ’„ Uncaught Exception:'), error.message); process.exit(1); }); process.on('unhandledRejection', (reason) => { console.error(chalk_1.default.red('šŸ’„ Unhandled Rejection:'), reason); process.exit(1); }); // Graceful shutdown process.on('SIGINT', () => { console.log(chalk_1.default.yellow('\nāš ļø Scan interrupted by user')); process.exit(0); }); process.on('SIGTERM', () => { console.log(chalk_1.default.yellow('\nāš ļø Scan terminated')); process.exit(0); }); // Start the CLI main().catch((error) => { console.error(chalk_1.default.red('šŸ’„ Fatal Error:'), error.message); process.exit(1); }); //# sourceMappingURL=index.js.map