diff --git a/src/ai/message-handlers.ts b/src/ai/message-handlers.ts index 1dc176f..e60ddb5 100644 --- a/src/ai/message-handlers.ts +++ b/src/ai/message-handlers.ts @@ -4,8 +4,6 @@ // it under the terms of the GNU Affero General Public License version 3 // as published by the Free Software Foundation. -// Pure functions for processing SDK message types - import { PentestError } from '../services/error-handling.js'; import { ErrorCode } from '../types/errors.js'; import { matchesBillingTextPattern } from '../utils/billing-detection.js'; @@ -263,14 +261,12 @@ function handleToolResultMessage(message: ToolResultMessage): ToolResultData { }; } -// Output helper for console logging function outputLines(lines: string[]): void { for (const line of lines) { console.log(line); } } -// Message dispatch result types export type MessageDispatchAction = | { type: 'continue'; apiErrorDetected?: boolean | undefined; model?: string | undefined } | { type: 'complete'; result: string | null; cost: number } diff --git a/src/ai/output-formatters.ts b/src/ai/output-formatters.ts index e1033a2..34735f2 100644 --- a/src/ai/output-formatters.ts +++ b/src/ai/output-formatters.ts @@ -4,14 +4,10 @@ // it under the terms of the GNU Affero General Public License version 3 // as published by the Free Software Foundation. -// Pure functions for formatting console output - import { extractAgentType, formatDuration } from '../utils/formatting.js'; import { AGENTS } from '../session-manager.js'; import type { ExecutionContext, ResultData } from './types.js'; -// --- Types for tool call filtering --- - interface ToolCallInput { url?: string; element?: string; @@ -32,8 +28,6 @@ interface ToolCall { input?: ToolCallInput; } -// --- Agent prefix logic --- - /** * Get agent prefix for parallel execution */ @@ -70,8 +64,6 @@ export function getAgentPrefix(description: string): string { return '[Agent]'; } -// --- Tool call filtering --- - /** * Extract domain from URL for display */ @@ -273,8 +265,6 @@ export function filterJsonToolCalls(content: string | null | undefined): string return processedLines.join('\n'); } -// --- Console output formatting --- - export function detectExecutionContext(description: string): ExecutionContext { const isParallelExecution = description.includes('vuln agent') || description.includes('exploit agent'); diff --git a/src/audit/audit-session.ts b/src/audit/audit-session.ts index 3fe389a..81313dd 100644 --- a/src/audit/audit-session.ts +++ b/src/audit/audit-session.ts @@ -112,24 +112,19 @@ export class AuditSession { await AgentLogger.savePrompt(this.sessionMetadata, agentName, promptContent); } - // Track current agent name for workflow logging this.currentAgentName = agentName; - // Create and initialize logger for this attempt this.currentLogger = new AgentLogger(this.sessionMetadata, agentName, attemptNumber); await this.currentLogger.initialize(); - // Start metrics tracking this.metricsTracker.startAgent(agentName, attemptNumber); - // Log start event await this.currentLogger.logEvent('agent_start', { agentName, attemptNumber, timestamp: formatTimestamp(), }); - // Log to unified workflow log await this.workflowLogger.logAgent(agentName, 'start', { attemptNumber }); } @@ -177,7 +172,6 @@ export class AuditSession { * End agent execution (mutex-protected) */ async endAgent(agentName: string, result: AgentEndResult): Promise { - // Log end event if (this.currentLogger) { await this.currentLogger.logEvent('agent_end', { agentName, @@ -187,15 +181,12 @@ export class AuditSession { timestamp: formatTimestamp(), }); - // Close logger await this.currentLogger.close(); this.currentLogger = null; } - // Reset current agent name this.currentAgentName = null; - // Log to unified workflow log const agentLogDetails: AgentLogDetails = { attemptNumber: result.attemptNumber, duration_ms: result.duration_ms, @@ -211,7 +202,6 @@ export class AuditSession { // Reload inside mutex to prevent lost updates during parallel exploitation phase await this.metricsTracker.reload(); - // Update metrics await this.metricsTracker.endAgent(agentName, result); } finally { unlock(); diff --git a/src/audit/logger.ts b/src/audit/logger.ts index 89b2347..d616bfa 100644 --- a/src/audit/logger.ts +++ b/src/audit/logger.ts @@ -42,7 +42,6 @@ export class AgentLogger { this.attemptNumber = attemptNumber; this.timestamp = Date.now(); - // Generate log file path and create stream const logPath = generateLogPath(sessionMetadata, agentName, this.timestamp, attemptNumber); this.logStream = new LogStream(logPath); } diff --git a/src/audit/metrics-tracker.ts b/src/audit/metrics-tracker.ts index 4827426..4bc40ec 100644 --- a/src/audit/metrics-tracker.ts +++ b/src/audit/metrics-tracker.ts @@ -227,7 +227,6 @@ export class MetricsTracker { // Recalculate aggregations this.recalculateAggregations(); - // Save to disk await this.save(); } diff --git a/src/audit/workflow-logger.ts b/src/audit/workflow-logger.ts index afd6b78..f01b80b 100644 --- a/src/audit/workflow-logger.ts +++ b/src/audit/workflow-logger.ts @@ -59,7 +59,6 @@ export class WorkflowLogger { return; } - // Open the stream (LogStream.open() handles directory creation) await this.logStream.open(); // Write header only if file is new (empty) diff --git a/src/config-parser.ts b/src/config-parser.ts index 57666e6..7aa08cb 100644 --- a/src/config-parser.ts +++ b/src/config-parser.ts @@ -22,11 +22,9 @@ import type { const require = createRequire(import.meta.url); const addFormats: FormatsPlugin = require('ajv-formats'); -// Initialize AJV with formats const ajv = new Ajv({ allErrors: true, verbose: true }); addFormats(ajv); -// Load JSON Schema let configSchema: object; let validateSchema: ValidateFunction; @@ -45,7 +43,6 @@ try { ); } -// Security patterns to block const DANGEROUS_PATTERNS: RegExp[] = [ /\.\.\//, // Path traversal /[<>]/, // HTML/XML injection @@ -179,10 +176,8 @@ function formatAjvErrors(errors: ErrorObject[]): string[] { return errors.map(formatAjvError); } -// Parse and load YAML configuration file with enhanced safety export const parseConfig = async (configPath: string): Promise => { try { - // File existence check if (!(await fs.pathExists(configPath))) { throw new PentestError( `Configuration file not found: ${configPath}`, @@ -193,7 +188,6 @@ export const parseConfig = async (configPath: string): Promise => { ); } - // File size check (prevent extremely large files) const stats = await fs.stat(configPath); const maxFileSize = 1024 * 1024; // 1MB if (stats.size > maxFileSize) { @@ -206,10 +200,8 @@ export const parseConfig = async (configPath: string): Promise => { ); } - // Read file content const configContent = await fs.readFile(configPath, 'utf8'); - // Basic content validation if (!configContent.trim()) { throw new PentestError( 'Configuration file is empty', @@ -220,7 +212,6 @@ export const parseConfig = async (configPath: string): Promise => { ); } - // Parse YAML with safety options let config: unknown; try { config = yaml.load(configContent, { @@ -239,7 +230,6 @@ export const parseConfig = async (configPath: string): Promise => { ); } - // Additional safety check if (config === null || config === undefined) { throw new PentestError( 'Configuration file resulted in null/undefined after parsing', @@ -250,7 +240,6 @@ export const parseConfig = async (configPath: string): Promise => { ); } - // Validate the configuration structure and content validateConfig(config as Config); return config as Config; @@ -259,7 +248,6 @@ export const parseConfig = async (configPath: string): Promise => { if (error instanceof PentestError) { throw error; } - // Wrap other errors with context const errMsg = error instanceof Error ? error.message : String(error); throw new PentestError( `Failed to parse configuration file '${configPath}': ${errMsg}`, @@ -271,9 +259,7 @@ export const parseConfig = async (configPath: string): Promise => { } }; -// Validate overall configuration structure using JSON Schema const validateConfig = (config: Config): void => { - // Basic structure validation if (!config || typeof config !== 'object') { throw new PentestError( 'Configuration must be a valid object', @@ -294,7 +280,6 @@ const validateConfig = (config: Config): void => { ); } - // JSON Schema validation const isValid = validateSchema(config); if (!isValid) { const errors = validateSchema.errors || []; @@ -308,10 +293,8 @@ const validateConfig = (config: Config): void => { ); } - // Additional security validation performSecurityValidation(config); - // Ensure at least some configuration is provided if (!config.rules && !config.authentication) { console.warn( '⚠️ Configuration file contains no rules or authentication. The pentest will run without any scoping restrictions or login capabilities.' @@ -323,9 +306,7 @@ const validateConfig = (config: Config): void => { } }; -// Perform additional security validation beyond JSON Schema const performSecurityValidation = (config: Config): void => { - // Validate authentication section for security issues if (config.authentication) { const auth = config.authentication; @@ -344,7 +325,6 @@ const performSecurityValidation = (config: Config): void => { } } - // Check for dangerous patterns in credentials if (auth.credentials) { for (const pattern of DANGEROUS_PATTERNS) { if (pattern.test(auth.credentials.username)) { @@ -368,7 +348,6 @@ const performSecurityValidation = (config: Config): void => { } } - // Check login flow for dangerous patterns if (auth.login_flow) { auth.login_flow.forEach((step, index) => { for (const pattern of DANGEROUS_PATTERNS) { @@ -386,24 +365,20 @@ const performSecurityValidation = (config: Config): void => { } } - // Validate rules section for security issues if (config.rules) { validateRulesSecurity(config.rules.avoid, 'avoid'); validateRulesSecurity(config.rules.focus, 'focus'); - // Check for duplicate and conflicting rules checkForDuplicates(config.rules.avoid || [], 'avoid'); checkForDuplicates(config.rules.focus || [], 'focus'); checkForConflicts(config.rules.avoid, config.rules.focus); } }; -// Validate rules for security issues const validateRulesSecurity = (rules: Rule[] | undefined, ruleType: string): void => { if (!rules) return; rules.forEach((rule, index) => { - // Security validation for (const pattern of DANGEROUS_PATTERNS) { if (pattern.test(rule.url_path)) { throw new PentestError( @@ -425,12 +400,10 @@ const validateRulesSecurity = (rules: Rule[] | undefined, ruleType: string): voi } } - // Type-specific validation validateRuleTypeSpecific(rule, ruleType, index); }); }; -// Validate rule based on its specific type const validateRuleTypeSpecific = (rule: Rule, ruleType: string, index: number): void => { const field = `rules.${ruleType}[${index}].url_path`; @@ -486,7 +459,6 @@ const validateRuleTypeSpecific = (rule: Rule, ruleType: string, index: number): } case 'header': - // Header name validation (basic) if (!rule.url_path.match(/^[a-zA-Z0-9\-_]+$/)) { throw new PentestError( `${field} for type 'header' must be a valid header name (alphanumeric, hyphens, underscores only)`, @@ -499,7 +471,6 @@ const validateRuleTypeSpecific = (rule: Rule, ruleType: string, index: number): break; case 'parameter': - // Parameter name validation (basic) if (!rule.url_path.match(/^[a-zA-Z0-9\-_]+$/)) { throw new PentestError( `${field} for type 'parameter' must be a valid parameter name (alphanumeric, hyphens, underscores only)`, @@ -513,7 +484,6 @@ const validateRuleTypeSpecific = (rule: Rule, ruleType: string, index: number): } }; -// Check for duplicate rules const checkForDuplicates = (rules: Rule[], ruleType: string): void => { const seen = new Set(); rules.forEach((rule, index) => { @@ -531,7 +501,6 @@ const checkForDuplicates = (rules: Rule[], ruleType: string): void => { }); }; -// Check for conflicting rules between avoid and focus const checkForConflicts = (avoidRules: Rule[] = [], focusRules: Rule[] = []): void => { const avoidSet = new Set(avoidRules.map((rule) => `${rule.type}:${rule.url_path}`)); @@ -549,7 +518,6 @@ const checkForConflicts = (avoidRules: Rule[] = [], focusRules: Rule[] = []): vo }); }; -// Sanitize and normalize rule values const sanitizeRule = (rule: Rule): Rule => { return { description: rule.description.trim(), @@ -558,7 +526,6 @@ const sanitizeRule = (rule: Rule): Rule => { }; }; -// Distribute configuration sections to different agents with sanitization export const distributeConfig = (config: Config | null): DistributedConfig => { const avoid = config?.rules?.avoid || []; const focus = config?.rules?.focus || []; @@ -571,7 +538,6 @@ export const distributeConfig = (config: Config | null): DistributedConfig => { }; }; -// Sanitize and normalize authentication values const sanitizeAuthentication = (auth: Authentication): Authentication => { return { login_type: auth.login_type.toLowerCase().trim() as Authentication['login_type'], diff --git a/src/services/error-handling.ts b/src/services/error-handling.ts index 098d32b..2f50d86 100644 --- a/src/services/error-handling.ts +++ b/src/services/error-handling.ts @@ -15,7 +15,6 @@ import { matchesBillingTextPattern, } from '../utils/billing-detection.js'; -// Custom error class for pentest operations export class PentestError extends Error { override name = 'PentestError' as const; type: PentestErrorType; @@ -43,8 +42,6 @@ export class PentestError extends Error { } } -// Handle tool execution errors -// Handle prompt loading errors export function handlePromptError( promptName: string, error: Error @@ -60,7 +57,6 @@ export function handlePromptError( }; } -// Patterns that indicate retryable errors const RETRYABLE_PATTERNS = [ // Network and connection errors 'network', @@ -104,12 +100,10 @@ const NON_RETRYABLE_PATTERNS = [ export function isRetryableError(error: Error): boolean { const message = error.message.toLowerCase(); - // Check for explicit non-retryable patterns first if (NON_RETRYABLE_PATTERNS.some((pattern) => message.includes(pattern))) { return false; } - // Check for retryable patterns return RETRYABLE_PATTERNS.some((pattern) => message.includes(pattern)); } diff --git a/src/temporal/activities.ts b/src/temporal/activities.ts index 3d264a8..80326d5 100644 --- a/src/temporal/activities.ts +++ b/src/temporal/activities.ts @@ -194,8 +194,6 @@ async function runAgentActivity( } } -// === Individual Agent Activity Exports === - export async function runPreReconAgent(input: ActivityInput): Promise { return runAgentActivity('pre-recon', input); } @@ -248,8 +246,6 @@ export async function runReportAgent(input: ActivityInput): Promise { const workspaceExists = await fileExists(sessionPath); if (workspaceExists) { - // === Resume Mode: existing workspace === isResume = true; console.log('=== RESUME MODE ==='); console.log(`Workspace: ${resumeFromWorkspace}\n`); @@ -255,7 +254,6 @@ async function startPipeline(): Promise { workflowId = `${resumeFromWorkspace}_resume_${Date.now()}`; sessionId = resumeFromWorkspace; } else { - // === New Named Workspace === if (!isValidWorkspaceName(resumeFromWorkspace)) { console.error(`ERROR: Invalid workspace name: "${resumeFromWorkspace}"`); console.error(' Must be 1-128 characters, alphanumeric/hyphens/underscores, starting with alphanumeric'); @@ -269,7 +267,6 @@ async function startPipeline(): Promise { sessionId = resumeFromWorkspace; } } else { - // === New Auto-Named Workflow === const hostname = sanitizeHostname(webUrl); workflowId = customWorkflowId || `${hostname}_shannon-${Date.now()}`; sessionId = workflowId; @@ -278,8 +275,8 @@ async function startPipeline(): Promise { const input: PipelineInput = { webUrl, repoPath, - workflowId, // Add for audit correlation - sessionId, // Workspace directory name + workflowId, + sessionId, ...(configPath && { configPath }), ...(outputPath && { outputPath }), ...(pipelineTestingMode && { pipelineTestingMode }), @@ -287,7 +284,6 @@ async function startPipeline(): Promise { ...(terminatedWorkflows.length > 0 && { terminatedWorkflows }), }; - // Determine output directory for display (use sessionId for persistent directory) // Use displayOutputPath (host path) if provided, otherwise fall back to outputPath or default const effectiveDisplayPath = displayOutputPath || outputPath || './audit-logs'; const outputDir = `${effectiveDisplayPath}/${sessionId}`; diff --git a/src/temporal/shared.ts b/src/temporal/shared.ts index 883b2e0..2a6d357 100644 --- a/src/temporal/shared.ts +++ b/src/temporal/shared.ts @@ -3,15 +3,13 @@ import { defineQuery } from '@temporalio/workflow'; export type { AgentMetrics } from '../types/metrics.js'; import type { AgentMetrics } from '../types/metrics.js'; -// === Types === - export interface PipelineInput { webUrl: string; repoPath: string; configPath?: string; outputPath?: string; pipelineTestingMode?: boolean; - workflowId?: string; // Added by client, used for audit correlation + workflowId?: string; // Used for audit correlation sessionId?: string; // Workspace directory name (distinct from workflowId for named workspaces) resumeFromWorkspace?: string; // Workspace name to resume from terminatedWorkflows?: string[]; // Workflows terminated during resume @@ -62,6 +60,4 @@ export interface VulnExploitPipelineResult { error: string | null; } -// === Queries === - export const getProgress = defineQuery('getProgress'); diff --git a/src/temporal/workflows.ts b/src/temporal/workflows.ts index edc5b94..695696f 100644 --- a/src/temporal/workflows.ts +++ b/src/temporal/workflows.ts @@ -105,11 +105,9 @@ export async function pentestPipelineWorkflow( ): Promise { const { workflowId } = workflowInfo(); - // Select activity proxy based on testing mode // Pipeline testing uses fast retry intervals (10s) for quick iteration const a = input.pipelineTestingMode ? testActs : acts; - // Workflow state (queryable) const state: PipelineState = { status: 'running', currentPhase: null, @@ -122,7 +120,6 @@ export async function pentestPipelineWorkflow( summary: null, }; - // Register query handler for real-time progress inspection setHandler(getProgress, (): PipelineProgress => ({ ...state, workflowId, @@ -147,18 +144,15 @@ export async function pentestPipelineWorkflow( }), }; - // === RESUME LOGIC === let resumeState: ResumeState | null = null; if (input.resumeFromWorkspace) { - // Load resume state from existing workspace resumeState = await a.loadResumeState( input.resumeFromWorkspace, input.webUrl, input.repoPath ); - // Restore git checkpoint and clean up partial deliverables const incompleteAgents = ALL_AGENTS.filter( (agentName) => !resumeState!.completedAgents.includes(agentName) ) as AgentName[]; @@ -169,7 +163,6 @@ export async function pentestPipelineWorkflow( incompleteAgents ); - // Check if all agents are already complete if (resumeState.completedAgents.length === ALL_AGENTS.length) { log.info(`All ${ALL_AGENTS.length} agents already completed. Nothing to resume.`); state.status = 'completed'; @@ -178,7 +171,6 @@ export async function pentestPipelineWorkflow( return state; } - // Record resume attempt in session.json and write resume header to workflow.log await a.recordResumeAttempt( activityInput, input.terminatedWorkflows || [], @@ -190,7 +182,6 @@ export async function pentestPipelineWorkflow( log.info('Resume state loaded and workspace restored'); } - // Helper to check if an agent should be skipped const shouldSkip = (agentName: string): boolean => { return resumeState?.completedAgents.includes(agentName) ?? false; }; @@ -413,7 +404,6 @@ export async function pentestPipelineWorkflow( state.completedAgents.push('report'); } - // === Complete === state.status = 'completed'; state.currentPhase = null; state.currentAgent = null; diff --git a/src/utils/metrics.ts b/src/utils/metrics.ts index d7f257a..55033db 100644 --- a/src/utils/metrics.ts +++ b/src/utils/metrics.ts @@ -4,8 +4,6 @@ // it under the terms of the GNU Affero General Public License version 3 // as published by the Free Software Foundation. -// Timing utilities - export class Timer { name: string; startTime: number;