endpoint-sentinel
Version:
User-friendly security scanner with interactive setup that scales from beginner to expert
305 lines ⢠15.7 kB
JavaScript
;
/**
* 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