From 13731f5ebfe7185f6d2ff3e7c9af9802260fd104 Mon Sep 17 00:00:00 2001 From: ajmallesh Date: Mon, 16 Feb 2026 11:30:00 -0800 Subject: [PATCH] refactor: remove ~750 lines of dead code across 12 files - Delete 4 dead files: pre-recon.ts, tool-checker.ts, input-validator.ts, environment.ts - Remove runClaudePromptWithRetry() and its now-unused imports from claude-executor.ts - De-export unused symbols: AGENT_ORDER, getParallelGroups, logError, isRouterMode, showHelp, displayTimingSummary - De-export unused types: ProcessingState, ProcessingResult, SdkMessage, MessageDispatchResult, MessageDispatchContext - Remove dead import (path from zx) in session-manager.ts and deprecated comment in config.ts --- src/ai/claude-executor.ts | 153 +-------------- src/ai/router-utils.ts | 6 - src/ai/types.ts | 10 +- src/cli/input-validator.ts | 59 ------ src/cli/ui.ts | 2 +- src/error-handling.ts | 3 +- src/phases/pre-recon.ts | 381 ------------------------------------- src/session-manager.ts | 24 --- src/setup/environment.ts | 56 ------ src/tool-checker.ts | 66 ------- src/types/config.ts | 2 +- src/utils/metrics.ts | 2 +- 12 files changed, 10 insertions(+), 754 deletions(-) delete mode 100644 src/cli/input-validator.ts delete mode 100644 src/phases/pre-recon.ts delete mode 100644 src/setup/environment.ts delete mode 100644 src/tool-checker.ts diff --git a/src/ai/claude-executor.ts b/src/ai/claude-executor.ts index ceab2d6..12b7f92 100644 --- a/src/ai/claude-executor.ts +++ b/src/ai/claude-executor.ts @@ -10,10 +10,9 @@ import { fs, path } from 'zx'; import chalk, { type ChalkInstance } from 'chalk'; import { query } from '@anthropic-ai/claude-agent-sdk'; -import { isRetryableError, getRetryDelay, PentestError } from '../error-handling.js'; +import { isRetryableError, PentestError } from '../error-handling.js'; import { timingResults, Timer } from '../utils/metrics.js'; import { formatTimestamp } from '../utils/formatting.js'; -import { createGitCheckpoint, commitGitSuccess, rollbackGitWorkspace, getGitCommitHash } from '../utils/git-manager.js'; import { AGENT_VALIDATORS, MCP_AGENT_MAPPING } from '../constants.js'; import { AuditSession } from '../audit/index.js'; import { createShannonHelperServer } from '../../mcp-server/dist/index.js'; @@ -403,153 +402,3 @@ async function processMessageStream( return { turnCount, result, apiErrorDetected, cost, model }; } - -// Main entry point for agent execution. Handles retries, git checkpoints, and validation. -export async function runClaudePromptWithRetry( - prompt: string, - sourceDir: string, - _allowedTools: string = 'Read', - context: string = '', - description: string = 'Claude analysis', - agentName: string | null = null, - colorFn: ChalkInstance = chalk.cyan, - sessionMetadata: SessionMetadata | null = null -): Promise { - const maxRetries = 3; - let lastError: Error | undefined; - let retryContext = context; - - console.log(chalk.cyan(`Starting ${description} with ${maxRetries} max attempts`)); - - let auditSession: AuditSession | null = null; - if (sessionMetadata && agentName) { - auditSession = new AuditSession(sessionMetadata); - await auditSession.initialize(); - } - - for (let attempt = 1; attempt <= maxRetries; attempt++) { - await createGitCheckpoint(sourceDir, description, attempt); - - if (auditSession && agentName) { - const fullPrompt = retryContext ? `${retryContext}\n\n${prompt}` : prompt; - await auditSession.startAgent(agentName, fullPrompt, attempt); - } - - try { - const result = await runClaudePrompt( - prompt, sourceDir, retryContext, - description, agentName, colorFn, sessionMetadata, auditSession, attempt - ); - - if (result.success) { - const validationPassed = await validateAgentOutput(result, agentName, sourceDir); - - if (validationPassed) { - if (result.apiErrorDetected) { - console.log(chalk.yellow(`Validation: Ready for exploitation despite API error warnings`)); - } - - if (auditSession && agentName) { - const commitHash = await getGitCommitHash(sourceDir); - const endResult: { - attemptNumber: number; - duration_ms: number; - cost_usd: number; - success: true; - checkpoint?: string; - } = { - attemptNumber: attempt, - duration_ms: result.duration, - cost_usd: result.cost || 0, - success: true, - }; - if (commitHash) { - endResult.checkpoint = commitHash; - } - await auditSession.endAgent(agentName, endResult); - } - - await commitGitSuccess(sourceDir, description); - console.log(chalk.green.bold(`${description} completed successfully on attempt ${attempt}/${maxRetries}`)); - return result; - // Validation failure is retryable - agent might succeed on retry with cleaner workspace - } else { - console.log(chalk.yellow(`${description} completed but output validation failed`)); - - if (auditSession && agentName) { - await auditSession.endAgent(agentName, { - attemptNumber: attempt, - duration_ms: result.duration, - cost_usd: result.partialCost || result.cost || 0, - success: false, - error: 'Output validation failed', - isFinalAttempt: attempt === maxRetries - }); - } - - if (result.apiErrorDetected) { - console.log(chalk.yellow(`API Error detected with validation failure - treating as retryable`)); - lastError = new Error('API Error: terminated with validation failure'); - } else { - lastError = new Error('Output validation failed'); - } - - if (attempt < maxRetries) { - await rollbackGitWorkspace(sourceDir, 'validation failure'); - continue; - } else { - throw new PentestError( - `Agent ${description} failed output validation after ${maxRetries} attempts. Required deliverable files were not created.`, - 'validation', - false, - { description, sourceDir, attemptsExhausted: maxRetries } - ); - } - } - } - - } catch (error) { - const err = error as Error & { duration?: number; cost?: number; partialResults?: unknown }; - lastError = err; - - if (auditSession && agentName) { - await auditSession.endAgent(agentName, { - attemptNumber: attempt, - duration_ms: err.duration || 0, - cost_usd: err.cost || 0, - success: false, - error: err.message, - isFinalAttempt: attempt === maxRetries - }); - } - - if (!isRetryableError(err)) { - console.log(chalk.red(`${description} failed with non-retryable error: ${err.message}`)); - await rollbackGitWorkspace(sourceDir, 'non-retryable error cleanup'); - throw err; - } - - if (attempt < maxRetries) { - await rollbackGitWorkspace(sourceDir, 'retryable error cleanup'); - - const delay = getRetryDelay(err, attempt); - const delaySeconds = (delay / 1000).toFixed(1); - console.log(chalk.yellow(`${description} failed (attempt ${attempt}/${maxRetries})`)); - console.log(chalk.gray(` Error: ${err.message}`)); - console.log(chalk.gray(` Workspace rolled back, retrying in ${delaySeconds}s...`)); - - if (err.partialResults) { - retryContext = `${context}\n\nPrevious partial results: ${JSON.stringify(err.partialResults)}`; - } - - await new Promise(resolve => setTimeout(resolve, delay)); - } else { - await rollbackGitWorkspace(sourceDir, 'final failure cleanup'); - console.log(chalk.red(`${description} failed after ${maxRetries} attempts`)); - console.log(chalk.red(` Final error: ${err.message}`)); - } - } - } - - throw lastError; -} diff --git a/src/ai/router-utils.ts b/src/ai/router-utils.ts index a2dbd06..df3d14b 100644 --- a/src/ai/router-utils.ts +++ b/src/ai/router-utils.ts @@ -26,9 +26,3 @@ export function getActualModelName(sdkReportedModel?: string): string | undefine return sdkReportedModel; } -/** - * Check if router mode is active. - */ -export function isRouterMode(): boolean { - return !!process.env.ANTHROPIC_BASE_URL && !!process.env.ROUTER_DEFAULT; -} diff --git a/src/ai/types.ts b/src/ai/types.ts index f742dd3..a327aa0 100644 --- a/src/ai/types.ts +++ b/src/ai/types.ts @@ -13,7 +13,7 @@ export interface ExecutionContext { agentKey: string; } -export interface ProcessingState { +interface ProcessingState { turnCount: number; result: string | null; apiErrorDetected: boolean; @@ -22,7 +22,7 @@ export interface ProcessingState { lastHeartbeat: number; } -export interface ProcessingResult { +interface ProcessingResult { result: string | null; turnCount: number; apiErrorDetected: boolean; @@ -111,7 +111,7 @@ export interface ApiErrorDetection { } // Message types from SDK stream -export type SdkMessage = +type SdkMessage = | AssistantMessage | ResultMessage | ToolUseMessage @@ -132,12 +132,12 @@ export interface UserMessage { } // Dispatch result types for message processing -export type MessageDispatchResult = +type MessageDispatchResult = | { action: 'continue' } | { action: 'break'; result: string | null; cost: number } | { action: 'throw'; error: Error }; -export interface MessageDispatchContext { +interface MessageDispatchContext { turnCount: number; execContext: ExecutionContext; description: string; diff --git a/src/cli/input-validator.ts b/src/cli/input-validator.ts deleted file mode 100644 index dfcb597..0000000 --- a/src/cli/input-validator.ts +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (C) 2025 Keygraph, Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License version 3 -// as published by the Free Software Foundation. - -import { fs, path } from 'zx'; - -interface ValidationResult { - valid: boolean; - error?: string; - path?: string; -} - -// Helper function: Validate web URL -export function validateWebUrl(url: string): ValidationResult { - try { - const parsed = new URL(url); - if (!['http:', 'https:'].includes(parsed.protocol)) { - return { valid: false, error: 'Web URL must use HTTP or HTTPS protocol' }; - } - if (!parsed.hostname) { - return { valid: false, error: 'Web URL must have a valid hostname' }; - } - return { valid: true }; - } catch { - return { valid: false, error: 'Invalid web URL format' }; - } -} - -// Helper function: Validate local repository path -export async function validateRepoPath(repoPath: string): Promise { - try { - // Check if path exists - if (!(await fs.pathExists(repoPath))) { - return { valid: false, error: 'Repository path does not exist' }; - } - - // Check if it's a directory - const stats = await fs.stat(repoPath); - if (!stats.isDirectory()) { - return { valid: false, error: 'Repository path must be a directory' }; - } - - // Check if it's readable - try { - await fs.access(repoPath, fs.constants.R_OK); - } catch { - return { valid: false, error: 'Repository path is not readable' }; - } - - // Convert to absolute path - const absolutePath = path.resolve(repoPath); - return { valid: true, path: absolutePath }; - } catch (error) { - const errMsg = error instanceof Error ? error.message : String(error); - return { valid: false, error: `Invalid repository path: ${errMsg}` }; - } -} diff --git a/src/cli/ui.ts b/src/cli/ui.ts index b059457..7ead9a9 100644 --- a/src/cli/ui.ts +++ b/src/cli/ui.ts @@ -8,7 +8,7 @@ import chalk from 'chalk'; import { displaySplashScreen } from '../splash-screen.js'; // Helper function: Display help information -export function showHelp(): void { +function showHelp(): void { console.log(chalk.cyan.bold('AI Penetration Testing Agent')); console.log(chalk.gray('Automated security assessment tool\n')); diff --git a/src/error-handling.ts b/src/error-handling.ts index ac605a5..c8d6b67 100644 --- a/src/error-handling.ts +++ b/src/error-handling.ts @@ -42,8 +42,7 @@ export class PentestError extends Error { } } -// Centralized error logging function -export async function logError( +async function logError( error: Error & { type?: PentestErrorType; retryable?: boolean; context?: PentestErrorContext }, contextMsg: string, sourceDir: string | null = null diff --git a/src/phases/pre-recon.ts b/src/phases/pre-recon.ts deleted file mode 100644 index 88dc3ed..0000000 --- a/src/phases/pre-recon.ts +++ /dev/null @@ -1,381 +0,0 @@ -// Copyright (C) 2025 Keygraph, Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License version 3 -// as published by the Free Software Foundation. - -import { $, fs, path } from 'zx'; -import chalk from 'chalk'; -import { Timer } from '../utils/metrics.js'; -import { formatDuration } from '../utils/formatting.js'; -import { handleToolError, PentestError } from '../error-handling.js'; -import { AGENTS } from '../session-manager.js'; -import { runClaudePromptWithRetry } from '../ai/claude-executor.js'; -import { loadPrompt } from '../prompts/prompt-manager.js'; -import type { ToolAvailability } from '../tool-checker.js'; -import type { DistributedConfig } from '../types/config.js'; - -interface AgentResult { - success: boolean; - duration: number; - cost?: number | undefined; - error?: string | undefined; - retryable?: boolean | undefined; -} - -type ToolName = 'nmap' | 'subfinder' | 'whatweb' | 'schemathesis'; -type ToolStatus = 'success' | 'skipped' | 'error'; - -interface TerminalScanResult { - tool: ToolName; - output: string; - status: ToolStatus; - duration: number; - success?: boolean; - error?: Error; -} - -interface PromptVariables { - webUrl: string; - repoPath: string; -} - -// Discriminated union for Wave1 tool results - clearer than loose union types -type Wave1ToolResult = - | { kind: 'scan'; result: TerminalScanResult } - | { kind: 'skipped'; message: string } - | { kind: 'agent'; result: AgentResult }; - -interface Wave1Results { - nmap: Wave1ToolResult; - subfinder: Wave1ToolResult; - whatweb: Wave1ToolResult; - naabu?: Wave1ToolResult; - codeAnalysis: AgentResult; -} - -interface Wave2Results { - schemathesis: TerminalScanResult; -} - -interface PreReconResult { - duration: number; - report: string; -} - -// Runs external security tools (nmap, whatweb, etc). Schemathesis requires schemas from code analysis. -async function runTerminalScan(tool: ToolName, target: string, sourceDir: string | null = null): Promise { - const timer = new Timer(`command-${tool}`); - try { - let result; - switch (tool) { - case 'nmap': { - console.log(chalk.blue(` šŸ” Running ${tool} scan...`)); - const nmapHostname = new URL(target).hostname; - result = await $({ silent: true, stdio: ['ignore', 'pipe', 'ignore'] })`nmap -sV -sC ${nmapHostname}`; - const duration = timer.stop(); - console.log(chalk.green(` āœ… ${tool} completed in ${formatDuration(duration)}`)); - return { tool: 'nmap', output: result.stdout, status: 'success', duration }; - } - case 'subfinder': { - console.log(chalk.blue(` šŸ” Running ${tool} scan...`)); - const hostname = new URL(target).hostname; - result = await $({ silent: true, stdio: ['ignore', 'pipe', 'ignore'] })`subfinder -d ${hostname}`; - const subfinderDuration = timer.stop(); - console.log(chalk.green(` āœ… ${tool} completed in ${formatDuration(subfinderDuration)}`)); - return { tool: 'subfinder', output: result.stdout, status: 'success', duration: subfinderDuration }; - } - case 'whatweb': { - console.log(chalk.blue(` šŸ” Running ${tool} scan...`)); - const command = `whatweb --open-timeout 30 --read-timeout 60 ${target}`; - console.log(chalk.gray(` Command: ${command}`)); - result = await $({ silent: true, stdio: ['ignore', 'pipe', 'ignore'] })`whatweb --open-timeout 30 --read-timeout 60 ${target}`; - const whatwebDuration = timer.stop(); - console.log(chalk.green(` āœ… ${tool} completed in ${formatDuration(whatwebDuration)}`)); - return { tool: 'whatweb', output: result.stdout, status: 'success', duration: whatwebDuration }; - } - case 'schemathesis': { - // Schemathesis depends on code analysis output - skip if no schemas found - const schemasDir = path.join(sourceDir || '.', 'outputs', 'schemas'); - if (await fs.pathExists(schemasDir)) { - const schemaFiles = await fs.readdir(schemasDir) as string[]; - const apiSchemas = schemaFiles.filter((f: string) => f.endsWith('.json') || f.endsWith('.yml') || f.endsWith('.yaml')); - if (apiSchemas.length > 0) { - console.log(chalk.blue(` šŸ” Running ${tool} scan...`)); - const allResults: string[] = []; - - // Run schemathesis on each schema file - for (const schemaFile of apiSchemas) { - const schemaPath = path.join(schemasDir, schemaFile); - try { - result = await $({ silent: true, stdio: ['ignore', 'pipe', 'ignore'] })`schemathesis run ${schemaPath} -u ${target} --max-failures=5`; - allResults.push(`Schema: ${schemaFile}\n${result.stdout}`); - } catch (schemaError) { - const err = schemaError as { stdout?: string; message?: string }; - allResults.push(`Schema: ${schemaFile}\nError: ${err.stdout || err.message}`); - } - } - - const schemaDuration = timer.stop(); - console.log(chalk.green(` āœ… ${tool} completed in ${formatDuration(schemaDuration)}`)); - return { tool: 'schemathesis', output: allResults.join('\n\n'), status: 'success', duration: schemaDuration }; - } else { - console.log(chalk.gray(` ā­ļø ${tool} - no API schemas found`)); - return { tool: 'schemathesis', output: 'No API schemas found', status: 'skipped', duration: timer.stop() }; - } - } else { - console.log(chalk.gray(` ā­ļø ${tool} - schemas directory not found`)); - return { tool: 'schemathesis', output: 'Schemas directory not found', status: 'skipped', duration: timer.stop() }; - } - } - default: - throw new Error(`Unknown tool: ${tool}`); - } - } catch (error) { - const duration = timer.stop(); - console.log(chalk.red(` āŒ ${tool} failed in ${formatDuration(duration)}`)); - return handleToolError(tool, error as Error & { code?: string }) as TerminalScanResult; - } -} - -// Wave 1: Initial footprinting + authentication -async function runPreReconWave1( - webUrl: string, - sourceDir: string, - variables: PromptVariables, - config: DistributedConfig | null, - pipelineTestingMode: boolean = false, - sessionId: string | null = null, - outputPath: string | null = null -): Promise { - console.log(chalk.blue(' → Launching Wave 1 operations in parallel...')); - - const operations: Promise[] = []; - - const skippedResult = (message: string): Wave1ToolResult => ({ kind: 'skipped', message }); - - // Skip external commands in pipeline testing mode - if (pipelineTestingMode) { - console.log(chalk.gray(' ā­ļø Skipping external tools (pipeline testing mode)')); - operations.push( - runClaudePromptWithRetry( - await loadPrompt('pre-recon-code', variables, null, pipelineTestingMode), - sourceDir, - '*', - '', - AGENTS['pre-recon'].displayName, - 'pre-recon', // Agent name for snapshot creation - chalk.cyan, - { id: sessionId!, webUrl, repoPath: sourceDir, ...(outputPath && { outputPath }) } // Session metadata for audit logging (STANDARD: use 'id' field) - ) - ); - const [codeAnalysis] = await Promise.all(operations); - return { - nmap: skippedResult('Skipped (pipeline testing mode)'), - subfinder: skippedResult('Skipped (pipeline testing mode)'), - whatweb: skippedResult('Skipped (pipeline testing mode)'), - codeAnalysis: codeAnalysis as AgentResult - }; - } else { - operations.push( - runTerminalScan('nmap', webUrl), - runTerminalScan('subfinder', webUrl), - runTerminalScan('whatweb', webUrl), - runClaudePromptWithRetry( - await loadPrompt('pre-recon-code', variables, null, pipelineTestingMode), - sourceDir, - '*', - '', - AGENTS['pre-recon'].displayName, - 'pre-recon', // Agent name for snapshot creation - chalk.cyan, - { id: sessionId!, webUrl, repoPath: sourceDir, ...(outputPath && { outputPath }) } // Session metadata for audit logging (STANDARD: use 'id' field) - ) - ); - } - - // Check if authentication config is provided for login instructions injection - console.log(chalk.gray(` → Config check: ${config ? 'present' : 'missing'}, Auth: ${config?.authentication ? 'present' : 'missing'}`)); - - const [nmap, subfinder, whatweb, codeAnalysis] = await Promise.all(operations); - - return { - nmap: { kind: 'scan', result: nmap as TerminalScanResult }, - subfinder: { kind: 'scan', result: subfinder as TerminalScanResult }, - whatweb: { kind: 'scan', result: whatweb as TerminalScanResult }, - codeAnalysis: codeAnalysis as AgentResult - }; -} - -// Wave 2: Additional scanning -async function runPreReconWave2( - webUrl: string, - sourceDir: string, - toolAvailability: ToolAvailability, - pipelineTestingMode: boolean = false -): Promise { - console.log(chalk.blue(' → Running Wave 2 additional scans in parallel...')); - - // Skip external commands in pipeline testing mode - if (pipelineTestingMode) { - console.log(chalk.gray(' ā­ļø Skipping external tools (pipeline testing mode)')); - return { - schemathesis: { tool: 'schemathesis', output: 'Skipped (pipeline testing mode)', status: 'skipped', duration: 0 } - }; - } - - const operations: Promise[] = []; - - // Parallel additional scans (only run if tools are available) - - if (toolAvailability.schemathesis) { - operations.push(runTerminalScan('schemathesis', webUrl, sourceDir)); - } - - // If no tools are available, return early - if (operations.length === 0) { - console.log(chalk.gray(' ā­ļø No Wave 2 tools available')); - return { - schemathesis: { tool: 'schemathesis', output: 'Tool not available', status: 'skipped', duration: 0 } - }; - } - - // Run all operations in parallel - const results = await Promise.all(operations); - - // Map results back to named properties - const response: Wave2Results = { - schemathesis: { tool: 'schemathesis', output: 'Tool not available', status: 'skipped', duration: 0 } - }; - let resultIndex = 0; - - if (toolAvailability.schemathesis) { - response.schemathesis = results[resultIndex++]!; - } else { - console.log(chalk.gray(' ā­ļø schemathesis - tool not available')); - } - - return response; -} - -// Extracts status and output from a Wave1 tool result -function extractResult(r: Wave1ToolResult | undefined): { status: string; output: string } { - if (!r) return { status: 'Skipped', output: 'No output' }; - switch (r.kind) { - case 'scan': - return { status: r.result.status || 'Skipped', output: r.result.output || 'No output' }; - case 'skipped': - return { status: 'Skipped', output: r.message }; - case 'agent': - return { status: r.result.success ? 'success' : 'error', output: 'See agent output' }; - } -} - -// Combines tool outputs into single deliverable. Falls back to reference if file missing. -async function stitchPreReconOutputs(wave1: Wave1Results, additionalScans: TerminalScanResult[], sourceDir: string): Promise { - // Try to read the code analysis deliverable file - let codeAnalysisContent = 'No analysis available'; - try { - const codeAnalysisPath = path.join(sourceDir, 'deliverables', 'code_analysis_deliverable.md'); - codeAnalysisContent = await fs.readFile(codeAnalysisPath, 'utf8'); - } catch (error) { - const err = error as Error; - console.log(chalk.yellow(`āš ļø Could not read code analysis deliverable: ${err.message}`)); - codeAnalysisContent = 'Analysis located in deliverables/code_analysis_deliverable.md'; - } - - // Build additional scans section - let additionalSection = ''; - if (additionalScans.length > 0) { - additionalSection = '\n## Authenticated Scans\n'; - for (const scan of additionalScans) { - additionalSection += ` -### ${scan.tool.toUpperCase()} -Status: ${scan.status} -${scan.output} -`; - } - } - - const nmap = extractResult(wave1.nmap); - const subfinder = extractResult(wave1.subfinder); - const whatweb = extractResult(wave1.whatweb); - const naabu = extractResult(wave1.naabu); - - const report = ` -# Pre-Reconnaissance Report - -## Port Discovery (naabu) -Status: ${naabu.status} -${naabu.output} - -## Network Scanning (nmap) -Status: ${nmap.status} -${nmap.output} - -## Subdomain Discovery (subfinder) -Status: ${subfinder.status} -${subfinder.output} - -## Technology Detection (whatweb) -Status: ${whatweb.status} -${whatweb.output} -## Code Analysis -${codeAnalysisContent} -${additionalSection} ---- -Report generated at: ${new Date().toISOString()} - `.trim(); - - // Ensure deliverables directory exists in the cloned repo - try { - const deliverablePath = path.join(sourceDir, 'deliverables', 'pre_recon_deliverable.md'); - await fs.ensureDir(path.join(sourceDir, 'deliverables')); - - // Write to file in the cloned repository - await fs.writeFile(deliverablePath, report); - } catch (error) { - const err = error as Error; - throw new PentestError( - `Failed to write pre-recon report: ${err.message}`, - 'filesystem', - false, - { sourceDir, originalError: err.message } - ); - } - - return report; -} - -// Main pre-recon phase execution function -export async function executePreReconPhase( - webUrl: string, - sourceDir: string, - variables: PromptVariables, - config: DistributedConfig | null, - toolAvailability: ToolAvailability, - pipelineTestingMode: boolean, - sessionId: string | null = null, - outputPath: string | null = null -): Promise { - console.log(chalk.yellow.bold('\nšŸ” PHASE 1: PRE-RECONNAISSANCE')); - const timer = new Timer('phase-1-pre-recon'); - - console.log(chalk.yellow('Wave 1: Initial footprinting...')); - const wave1Results = await runPreReconWave1(webUrl, sourceDir, variables, config, pipelineTestingMode, sessionId, outputPath); - console.log(chalk.green(' āœ… Wave 1 operations completed')); - - console.log(chalk.yellow('Wave 2: Additional scanning...')); - const wave2Results = await runPreReconWave2(webUrl, sourceDir, toolAvailability, pipelineTestingMode); - console.log(chalk.green(' āœ… Wave 2 operations completed')); - - console.log(chalk.blue('šŸ“ Stitching pre-recon outputs...')); - const additionalScans = wave2Results.schemathesis ? [wave2Results.schemathesis] : []; - const preReconReport = await stitchPreReconOutputs(wave1Results, additionalScans, sourceDir); - const duration = timer.stop(); - - console.log(chalk.green(`āœ… Pre-reconnaissance complete in ${formatDuration(duration)}`)); - console.log(chalk.green(`šŸ’¾ Saved to ${sourceDir}/deliverables/pre_recon_deliverable.md`)); - - return { duration, report: preReconReport }; -} diff --git a/src/session-manager.ts b/src/session-manager.ts index 335a74d..73c9095 100644 --- a/src/session-manager.ts +++ b/src/session-manager.ts @@ -4,7 +4,6 @@ // it under the terms of the GNU Affero General Public License version 3 // as published by the Free Software Foundation. -import { path } from 'zx'; import type { AgentName } from './types/index.js'; // Agent definition interface @@ -83,29 +82,6 @@ export const AGENTS: Readonly> = Object.freez } }); -// Agent execution order -export const AGENT_ORDER: readonly AgentName[] = Object.freeze([ - 'pre-recon', - 'recon', - 'injection-vuln', - 'xss-vuln', - 'auth-vuln', - 'ssrf-vuln', - 'authz-vuln', - 'injection-exploit', - 'xss-exploit', - 'auth-exploit', - 'ssrf-exploit', - 'authz-exploit', - 'report' -] as const); - -// Parallel execution groups -export const getParallelGroups = (): Readonly<{ vuln: AgentName[]; exploit: AgentName[] }> => Object.freeze({ - vuln: ['injection-vuln', 'xss-vuln', 'auth-vuln', 'ssrf-vuln', 'authz-vuln'], - exploit: ['injection-exploit', 'xss-exploit', 'auth-exploit', 'ssrf-exploit', 'authz-exploit'] -}); - // Phase names for metrics aggregation export type PhaseName = 'pre-recon' | 'recon' | 'vulnerability-analysis' | 'exploitation' | 'reporting'; diff --git a/src/setup/environment.ts b/src/setup/environment.ts deleted file mode 100644 index 55f8bbe..0000000 --- a/src/setup/environment.ts +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (C) 2025 Keygraph, Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License version 3 -// as published by the Free Software Foundation. - -import { $, fs, path } from 'zx'; -import chalk from 'chalk'; -import { PentestError } from '../error-handling.js'; - -// Pure function: Setup local repository for testing -export async function setupLocalRepo(repoPath: string): Promise { - try { - const sourceDir = path.resolve(repoPath); - - // MCP servers are now configured via mcpServers option in claude-executor.js - // No need for pre-setup with claude CLI - - // Initialize git repository if not already initialized and create checkpoint - try { - // Check if it's already a git repository - const isGitRepo = await fs.pathExists(path.join(sourceDir, '.git')); - - if (!isGitRepo) { - await $`cd ${sourceDir} && git init`; - console.log(chalk.blue('āœ… Git repository initialized')); - } - - // Configure git for pentest agent - await $`cd ${sourceDir} && git config user.name "Pentest Agent"`; - await $`cd ${sourceDir} && git config user.email "agent@localhost"`; - - // Create initial checkpoint - await $`cd ${sourceDir} && git add -A && git commit -m "Initial checkpoint: Local repository setup" --allow-empty`; - console.log(chalk.green('āœ… Initial checkpoint created')); - } catch (gitError) { - const errMsg = gitError instanceof Error ? gitError.message : String(gitError); - console.log(chalk.yellow(`āš ļø Git setup warning: ${errMsg}`)); - // Non-fatal - continue without Git setup - } - - // MCP tools (save_deliverable, generate_totp) are now available natively via shannon-helper MCP server - // No need to copy bash scripts to target repository - - return sourceDir; - } catch (error) { - if (error instanceof PentestError) { - throw error; - } - const errMsg = error instanceof Error ? error.message : String(error); - throw new PentestError(`Local repository setup failed: ${errMsg}`, 'filesystem', false, { - repoPath, - originalError: errMsg, - }); - } -} diff --git a/src/tool-checker.ts b/src/tool-checker.ts deleted file mode 100644 index 6340575..0000000 --- a/src/tool-checker.ts +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (C) 2025 Keygraph, Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License version 3 -// as published by the Free Software Foundation. - -import { $ } from 'zx'; -import chalk from 'chalk'; - -type ToolName = 'nmap' | 'subfinder' | 'whatweb' | 'schemathesis'; - -export type ToolAvailability = Record; - -// Check availability of required tools -export const checkToolAvailability = async (): Promise => { - const tools: ToolName[] = ['nmap', 'subfinder', 'whatweb', 'schemathesis']; - const availability: ToolAvailability = { - nmap: false, - subfinder: false, - whatweb: false, - schemathesis: false - }; - - console.log(chalk.blue('šŸ”§ Checking tool availability...')); - - for (const tool of tools) { - try { - await $`command -v ${tool}`; - availability[tool] = true; - console.log(chalk.green(` āœ… ${tool} - available`)); - } catch { - availability[tool] = false; - console.log(chalk.yellow(` āš ļø ${tool} - not found`)); - } - } - - return availability; -}; - -// Handle missing tools with user-friendly messages -export const handleMissingTools = (toolAvailability: ToolAvailability): ToolName[] => { - const missing = (Object.entries(toolAvailability) as Array<[ToolName, boolean]>) - .filter(([, available]) => !available) - .map(([tool]) => tool); - - if (missing.length > 0) { - console.log(chalk.yellow(`\nāš ļø Missing tools: ${missing.join(', ')}`)); - console.log(chalk.gray('Some functionality will be limited. Install missing tools for full capability.')); - - // Provide installation hints - const installHints: Record = { - 'nmap': 'brew install nmap (macOS) or apt install nmap (Ubuntu)', - 'subfinder': 'go install -v github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest', - 'whatweb': 'gem install whatweb', - 'schemathesis': 'pip install schemathesis' - }; - - console.log(chalk.gray('\nInstallation hints:')); - missing.forEach(tool => { - console.log(chalk.gray(` ${tool}: ${installHints[tool]}`)); - }); - console.log(''); - } - - return missing; -}; diff --git a/src/types/config.ts b/src/types/config.ts index 548a979..18feb42 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -53,7 +53,7 @@ export interface Authentication { export interface Config { rules?: Rules; authentication?: Authentication; - login?: unknown; // Deprecated + login?: unknown; } export interface DistributedConfig { diff --git a/src/utils/metrics.ts b/src/utils/metrics.ts index 01cf79c..ca1a8f8 100644 --- a/src/utils/metrics.ts +++ b/src/utils/metrics.ts @@ -60,7 +60,7 @@ export const costResults: CostResults = { }; // Function to display comprehensive timing summary -export const displayTimingSummary = (): void => { +const displayTimingSummary = (): void => { if (!timingResults.total) { console.log(chalk.yellow('No timing data available')); return;