From 8e4fafba992efb94080dbaa8f4a40cbf78d6e767 Mon Sep 17 00:00:00 2001 From: ajmallesh Date: Mon, 16 Feb 2026 11:55:59 -0800 Subject: [PATCH] refactor: remove ~275 lines of dead code and enable stricter tsconfig - Delete unused src/cli/ui.ts, remove zod dependency, drop 4 dead functions (logError, handleToolError, getRetryDelay, displayTimingSummary) - Remove 8 unused types/interfaces and 3 duplicate formatting utils from audit/utils.ts - Narrow export surface: make 7 message-handler functions private, remove unused audit re-exports, unexport AgentDefinition and path constants - Remove unused runClaudePrompt params (sessionMetadata, attemptNumber) and update caller - Enable tsconfig noUnusedLocals, noUnusedParameters, noImplicitReturns, noImplicitOverride, noFallthroughCasesInSwitch --- package-lock.json | 1 - package.json | 1 - src/ai/claude-executor.ts | 5 +- src/ai/message-handlers.ts | 14 +++--- src/ai/types.ts | 38 --------------- src/audit/index.ts | 4 -- src/audit/utils.ts | 37 +------------- src/cli/ui.ts | 49 ------------------- src/config-parser.ts | 1 - src/error-handling.ts | 98 +------------------------------------- src/session-manager.ts | 2 +- src/temporal/activities.ts | 4 +- src/types/config.ts | 4 +- src/utils/metrics.ts | 52 -------------------- tsconfig.json | 10 ++-- 15 files changed, 20 insertions(+), 300 deletions(-) delete mode 100644 src/cli/ui.ts diff --git a/package-lock.json b/package-lock.json index b309592..9d2b980 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,6 @@ "figlet": "^1.9.3", "gradient-string": "^3.0.0", "js-yaml": "^4.1.0", - "zod": "^4.3.6", "zx": "^8.0.0" }, "devDependencies": { diff --git a/package.json b/package.json index 78a50ee..8758b3f 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,6 @@ "figlet": "^1.9.3", "gradient-string": "^3.0.0", "js-yaml": "^4.1.0", - "zod": "^4.3.6", "zx": "^8.0.0" }, "devDependencies": { diff --git a/src/ai/claude-executor.ts b/src/ai/claude-executor.ts index 12b7f92..a2321d9 100644 --- a/src/ai/claude-executor.ts +++ b/src/ai/claude-executor.ts @@ -16,7 +16,6 @@ import { formatTimestamp } from '../utils/formatting.js'; import { AGENT_VALIDATORS, MCP_AGENT_MAPPING } from '../constants.js'; import { AuditSession } from '../audit/index.js'; import { createShannonHelperServer } from '../../mcp-server/dist/index.js'; -import type { SessionMetadata } from '../audit/utils.js'; import { getPromptNameForAgent } from '../types/agents.js'; import type { AgentName } from '../types/index.js'; @@ -200,9 +199,7 @@ export async function runClaudePrompt( description: string = 'Claude analysis', agentName: string | null = null, colorFn: ChalkInstance = chalk.cyan, - sessionMetadata: SessionMetadata | null = null, - auditSession: AuditSession | null = null, - attemptNumber: number = 1 + auditSession: AuditSession | null = null ): Promise { const timer = new Timer(`agent-${description.toLowerCase().replace(/\s+/g, '-')}`); const fullPrompt = context ? `${context}\n\n${prompt}` : prompt; diff --git a/src/ai/message-handlers.ts b/src/ai/message-handlers.ts index 57cdd4e..f5113d7 100644 --- a/src/ai/message-handlers.ts +++ b/src/ai/message-handlers.ts @@ -38,7 +38,7 @@ import type { import type { ChalkInstance } from 'chalk'; // Handles both array and string content formats from SDK -export function extractMessageContent(message: AssistantMessage): string { +function extractMessageContent(message: AssistantMessage): string { const messageContent = message.message; if (Array.isArray(messageContent.content)) { @@ -51,7 +51,7 @@ export function extractMessageContent(message: AssistantMessage): string { } // Extracts only text content (no tool_use JSON) to avoid false positives in error detection -export function extractTextOnlyContent(message: AssistantMessage): string { +function extractTextOnlyContent(message: AssistantMessage): string { const messageContent = message.message; if (Array.isArray(messageContent.content)) { @@ -64,7 +64,7 @@ export function extractTextOnlyContent(message: AssistantMessage): string { return String(messageContent.content); } -export function detectApiError(content: string): ApiErrorDetection { +function detectApiError(content: string): ApiErrorDetection { if (!content || typeof content !== 'string') { return { detected: false }; } @@ -181,7 +181,7 @@ function handleStructuredError( } } -export function handleAssistantMessage( +function handleAssistantMessage( message: AssistantMessage, turnCount: number ): AssistantResult { @@ -219,7 +219,7 @@ export function handleAssistantMessage( } // Final message of a query with cost/duration info -export function handleResultMessage(message: ResultMessage): ResultData { +function handleResultMessage(message: ResultMessage): ResultData { const result: ResultData = { result: message.result || null, cost: message.total_cost_usd || 0, @@ -243,7 +243,7 @@ export function handleResultMessage(message: ResultMessage): ResultData { return result; } -export function handleToolUseMessage(message: ToolUseMessage): ToolUseData { +function handleToolUseMessage(message: ToolUseMessage): ToolUseData { return { toolName: message.name, parameters: message.input || {}, @@ -252,7 +252,7 @@ export function handleToolUseMessage(message: ToolUseMessage): ToolUseData { } // Truncates long results for display (500 char limit), preserves full content for logging -export function handleToolResultMessage(message: ToolResultMessage): ToolResultData { +function handleToolResultMessage(message: ToolResultMessage): ToolResultData { const content = message.content; const contentStr = typeof content === 'string' ? content : JSON.stringify(content, null, 2); diff --git a/src/ai/types.ts b/src/ai/types.ts index a327aa0..af1a6f6 100644 --- a/src/ai/types.ts +++ b/src/ai/types.ts @@ -13,22 +13,6 @@ export interface ExecutionContext { agentKey: string; } -interface ProcessingState { - turnCount: number; - result: string | null; - apiErrorDetected: boolean; - totalCost: number; - partialCost: number; - lastHeartbeat: number; -} - -interface ProcessingResult { - result: string | null; - turnCount: number; - apiErrorDetected: boolean; - totalCost: number; -} - export interface AssistantResult { content: string; cleanedContent: string; @@ -110,15 +94,6 @@ export interface ApiErrorDetection { shouldThrow?: Error; } -// Message types from SDK stream -type SdkMessage = - | AssistantMessage - | ResultMessage - | ToolUseMessage - | ToolResultMessage - | SystemInitMessage - | UserMessage; - export interface SystemInitMessage { type: 'system'; subtype: 'init'; @@ -131,16 +106,3 @@ export interface UserMessage { type: 'user'; } -// Dispatch result types for message processing -type MessageDispatchResult = - | { action: 'continue' } - | { action: 'break'; result: string | null; cost: number } - | { action: 'throw'; error: Error }; - -interface MessageDispatchContext { - turnCount: number; - execContext: ExecutionContext; - description: string; - colorFn: (text: string) => string; - useCleanOutput: boolean; -} diff --git a/src/audit/index.ts b/src/audit/index.ts index 834a8a1..97d3b7d 100644 --- a/src/audit/index.ts +++ b/src/audit/index.ts @@ -17,7 +17,3 @@ */ export { AuditSession } from './audit-session.js'; -export { AgentLogger } from './logger.js'; -export { WorkflowLogger } from './workflow-logger.js'; -export { MetricsTracker } from './metrics-tracker.js'; -export * as AuditUtils from './utils.js'; diff --git a/src/audit/utils.ts b/src/audit/utils.ts index b518c93..4b024d3 100644 --- a/src/audit/utils.ts +++ b/src/audit/utils.ts @@ -19,8 +19,8 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Get Shannon repository root -export const SHANNON_ROOT = path.resolve(__dirname, '..', '..'); -export const AUDIT_LOGS_DIR = path.join(SHANNON_ROOT, 'audit-logs'); +const SHANNON_ROOT = path.resolve(__dirname, '..', '..'); +const AUDIT_LOGS_DIR = path.join(SHANNON_ROOT, 'audit-logs'); export interface SessionMetadata { id: string; @@ -132,39 +132,6 @@ export async function atomicWrite(filePath: string, data: object | string): Prom } } -/** - * Format duration in milliseconds to human-readable string - */ -export function formatDuration(ms: number): string { - if (ms < 1000) { - return `${ms}ms`; - } - - const seconds = ms / 1000; - if (seconds < 60) { - return `${seconds.toFixed(1)}s`; - } - - const minutes = Math.floor(seconds / 60); - const remainingSeconds = Math.floor(seconds % 60); - return `${minutes}m ${remainingSeconds}s`; -} - -/** - * Format timestamp to ISO 8601 string - */ -export function formatTimestamp(timestamp: number = Date.now()): string { - return new Date(timestamp).toISOString(); -} - -/** - * Calculate percentage - */ -export function calculatePercentage(part: number, total: number): number { - if (total === 0) return 0; - return (part / total) * 100; -} - /** * Read and parse JSON file */ diff --git a/src/cli/ui.ts b/src/cli/ui.ts deleted file mode 100644 index 7ead9a9..0000000 --- a/src/cli/ui.ts +++ /dev/null @@ -1,49 +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 chalk from 'chalk'; -import { displaySplashScreen } from '../splash-screen.js'; - -// Helper function: Display help information -function showHelp(): void { - console.log(chalk.cyan.bold('AI Penetration Testing Agent')); - console.log(chalk.gray('Automated security assessment tool\n')); - - console.log(chalk.yellow.bold('USAGE:')); - console.log(' shannon [--config config.yaml] [--output /path/to/reports]\n'); - - console.log(chalk.yellow.bold('OPTIONS:')); - console.log( - ' --config YAML configuration file for authentication and testing parameters' - ); - console.log( - ' --output Custom output directory for session folder (default: ./audit-logs/)' - ); - console.log( - ' --pipeline-testing Use minimal prompts for fast pipeline testing (creates minimal deliverables)' - ); - console.log( - ' --disable-loader Disable the animated progress loader (useful when logs interfere with spinner)' - ); - console.log(' --help Show this help message\n'); - - console.log(chalk.yellow.bold('EXAMPLES:')); - console.log(' shannon "https://example.com" "/path/to/local/repo"'); - console.log(' shannon "https://example.com" "/path/to/local/repo" --config auth.yaml'); - console.log(' shannon "https://example.com" "/path/to/local/repo" --output /path/to/reports'); - console.log(' shannon "https://example.com" "/path/to/local/repo" --pipeline-testing\n'); - - console.log(chalk.yellow.bold('REQUIREMENTS:')); - console.log(' • WEB_URL must start with http:// or https://'); - console.log(' • REPO_PATH must be an accessible local directory'); - console.log(' • Only test systems you own or have permission to test\n'); - - console.log(chalk.yellow.bold('ENVIRONMENT VARIABLES:')); - console.log(' PENTEST_MAX_RETRIES Number of retries for AI agents (default: 3)'); -} - -// Export the splash screen function for use in main -export { displaySplashScreen }; diff --git a/src/config-parser.ts b/src/config-parser.ts index 610fb3a..0c56072 100644 --- a/src/config-parser.ts +++ b/src/config-parser.ts @@ -13,7 +13,6 @@ import { PentestError } from './error-handling.js'; import type { Config, Rule, - Rules, Authentication, DistributedConfig, } from './types/config.js'; diff --git a/src/error-handling.ts b/src/error-handling.ts index c8d6b67..bb8b4c5 100644 --- a/src/error-handling.ts +++ b/src/error-handling.ts @@ -4,25 +4,15 @@ // it under the terms of the GNU Affero General Public License version 3 // as published by the Free Software Foundation. -import chalk from 'chalk'; -import { fs, path } from 'zx'; import type { PentestErrorType, PentestErrorContext, - LogEntry, - ToolErrorResult, PromptErrorResult, } from './types/errors.js'; -// Temporal error classification for ApplicationFailure wrapping -export interface TemporalErrorClassification { - type: string; - retryable: boolean; -} - // Custom error class for pentest operations export class PentestError extends Error { - name = 'PentestError' as const; + override name = 'PentestError' as const; type: PentestErrorType; retryable: boolean; context: PentestErrorContext; @@ -42,76 +32,7 @@ export class PentestError extends Error { } } -async function logError( - error: Error & { type?: PentestErrorType; retryable?: boolean; context?: PentestErrorContext }, - contextMsg: string, - sourceDir: string | null = null -): Promise { - const timestamp = new Date().toISOString(); - const logEntry: LogEntry = { - timestamp, - context: contextMsg, - error: { - name: error.name || error.constructor.name, - message: error.message, - type: error.type || 'unknown', - retryable: error.retryable || false, - }, - }; - // Only add stack if it exists - if (error.stack) { - logEntry.error.stack = error.stack; - } - - // Console logging with color - const prefix = error.retryable ? '⚠️' : '❌'; - const color = error.retryable ? chalk.yellow : chalk.red; - console.log(color(`${prefix} ${contextMsg}:`)); - console.log(color(` ${error.message}`)); - - if (error.context && Object.keys(error.context).length > 0) { - console.log(chalk.gray(` Context: ${JSON.stringify(error.context)}`)); - } - - // File logging (if source directory available) - if (sourceDir) { - try { - const logPath = path.join(sourceDir, 'error.log'); - await fs.appendFile(logPath, JSON.stringify(logEntry) + '\n'); - } catch (logErr) { - const errMsg = logErr instanceof Error ? logErr.message : String(logErr); - console.log(chalk.gray(` (Failed to write error log: ${errMsg})`)); - } - } - - return logEntry; -} - // Handle tool execution errors -export function handleToolError( - toolName: string, - error: Error & { code?: string } -): ToolErrorResult { - const isRetryable = - error.code === 'ECONNRESET' || - error.code === 'ETIMEDOUT' || - error.code === 'ENOTFOUND'; - - return { - tool: toolName, - output: `Error: ${error.message}`, - status: 'error', - duration: 0, - success: false, - error: new PentestError( - `${toolName} execution failed: ${error.message}`, - 'tool', - isRetryable, - { toolName, originalError: error.message, errorCode: error.code } - ), - }; -} - // Handle prompt loading errors export function handlePromptError( promptName: string, @@ -181,21 +102,6 @@ export function isRetryableError(error: Error): boolean { return RETRYABLE_PATTERNS.some((pattern) => message.includes(pattern)); } -// Rate limit errors get longer base delay (30s) vs standard exponential backoff (2s) -export function getRetryDelay(error: Error, attempt: number): number { - const message = error.message.toLowerCase(); - - // Rate limiting gets longer delays - if (message.includes('rate limit') || message.includes('429')) { - return Math.min(30000 + attempt * 10000, 120000); // 30s, 40s, 50s, max 2min - } - - // Exponential backoff with jitter for other retryable errors - const baseDelay = Math.pow(2, attempt) * 1000; // 2s, 4s, 8s - const jitter = Math.random() * 1000; // 0-1s random - return Math.min(baseDelay + jitter, 30000); // Max 30s -} - /** * Classifies errors for Temporal workflow retry behavior. * Returns error type and whether Temporal should retry. @@ -204,7 +110,7 @@ export function getRetryDelay(error: Error, attempt: number): number { * - Retryable errors: Temporal retries with configured backoff * - Non-retryable errors: Temporal fails immediately */ -export function classifyErrorForTemporal(error: unknown): TemporalErrorClassification { +export function classifyErrorForTemporal(error: unknown): { type: string; retryable: boolean } { const message = (error instanceof Error ? error.message : String(error)).toLowerCase(); // === BILLING ERRORS (Retryable with long backoff) === diff --git a/src/session-manager.ts b/src/session-manager.ts index 73c9095..5733740 100644 --- a/src/session-manager.ts +++ b/src/session-manager.ts @@ -7,7 +7,7 @@ import type { AgentName } from './types/index.js'; // Agent definition interface -export interface AgentDefinition { +interface AgentDefinition { name: AgentName; displayName: string; prerequisites: AgentName[]; diff --git a/src/temporal/activities.ts b/src/temporal/activities.ts index 6f76244..1a2252e 100644 --- a/src/temporal/activities.ts +++ b/src/temporal/activities.ts @@ -181,9 +181,7 @@ async function runAgentActivity( agentName, // description agentName, chalk.cyan, - sessionMetadata, - auditSession, - attemptNumber + auditSession ); // 6.5. Sanity check: Detect spending cap that slipped through all detection layers diff --git a/src/types/config.ts b/src/types/config.ts index 18feb42..0262a59 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -29,10 +29,8 @@ export interface Rules { export type LoginType = 'form' | 'sso' | 'api' | 'basic'; -export type SuccessConditionType = 'url' | 'cookie' | 'element' | 'redirect'; - export interface SuccessCondition { - type: SuccessConditionType; + type: 'url' | 'cookie' | 'element' | 'redirect'; value: string; } diff --git a/src/utils/metrics.ts b/src/utils/metrics.ts index ca1a8f8..a02836d 100644 --- a/src/utils/metrics.ts +++ b/src/utils/metrics.ts @@ -4,9 +4,6 @@ // it under the terms of the GNU Affero General Public License version 3 // as published by the Free Software Foundation. -import chalk from 'chalk'; -import { formatDuration } from './formatting.js'; - // Timing utilities export class Timer { @@ -59,52 +56,3 @@ export const costResults: CostResults = { total: 0, }; -// Function to display comprehensive timing summary -const displayTimingSummary = (): void => { - if (!timingResults.total) { - console.log(chalk.yellow('No timing data available')); - return; - } - - const totalDuration = timingResults.total.stop(); - - console.log(chalk.cyan.bold('\n⏱️ TIMING SUMMARY')); - console.log(chalk.gray('─'.repeat(60))); - - // Total execution time - console.log(chalk.cyan(`📊 Total Execution Time: ${formatDuration(totalDuration)}`)); - console.log(); - - // Agent breakdown - if (Object.keys(timingResults.agents).length > 0) { - console.log(chalk.magenta.bold('🤖 Agent Breakdown:')); - let agentTotal = 0; - for (const [agent, duration] of Object.entries(timingResults.agents)) { - const percentage = ((duration / totalDuration) * 100).toFixed(1); - const displayName = agent.replace(/-/g, ' '); - console.log( - chalk.magenta( - ` ${displayName.padEnd(20)} ${formatDuration(duration).padStart(8)} (${percentage}%)` - ) - ); - agentTotal += duration; - } - console.log( - chalk.gray( - ` ${'Agents Total'.padEnd(20)} ${formatDuration(agentTotal).padStart(8)} (${((agentTotal / totalDuration) * 100).toFixed(1)}%)` - ) - ); - } - - // Cost breakdown - if (Object.keys(costResults.agents).length > 0) { - console.log(chalk.green.bold('\n💰 Cost Breakdown:')); - for (const [agent, cost] of Object.entries(costResults.agents)) { - const displayName = agent.replace(/-/g, ' '); - console.log(chalk.green(` ${displayName.padEnd(20)} $${cost.toFixed(4).padStart(8)}`)); - } - console.log(chalk.gray(` ${'Total Cost'.padEnd(20)} $${costResults.total.toFixed(4).padStart(8)}`)); - } - - console.log(chalk.gray('─'.repeat(60))); -}; diff --git a/tsconfig.json b/tsconfig.json index f56a026..1222629 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -33,11 +33,11 @@ "exactOptionalPropertyTypes": true, // Style Options - // "noImplicitReturns": true, - // "noImplicitOverride": true, - // "noUnusedLocals": true, - // "noUnusedParameters": true, - // "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noImplicitOverride": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, // "noPropertyAccessFromIndexSignature": true, // Recommended Options