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
This commit is contained in:
ajmallesh
2026-02-16 11:55:59 -08:00
parent 13731f5ebf
commit 8e4fafba99
15 changed files with 20 additions and 300 deletions
-1
View File
@@ -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": {
-1
View File
@@ -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": {
+1 -4
View File
@@ -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<ClaudePromptResult> {
const timer = new Timer(`agent-${description.toLowerCase().replace(/\s+/g, '-')}`);
const fullPrompt = context ? `${context}\n\n${prompt}` : prompt;
+7 -7
View File
@@ -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);
-38
View File
@@ -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;
}
-4
View File
@@ -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';
+2 -35
View File
@@ -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
*/
-49
View File
@@ -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 <WEB_URL> <REPO_PATH> [--config config.yaml] [--output /path/to/reports]\n');
console.log(chalk.yellow.bold('OPTIONS:'));
console.log(
' --config <file> YAML configuration file for authentication and testing parameters'
);
console.log(
' --output <path> 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 };
-1
View File
@@ -13,7 +13,6 @@ import { PentestError } from './error-handling.js';
import type {
Config,
Rule,
Rules,
Authentication,
DistributedConfig,
} from './types/config.js';
+2 -96
View File
@@ -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<LogEntry> {
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) ===
+1 -1
View File
@@ -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[];
+1 -3
View File
@@ -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
+1 -3
View File
@@ -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;
}
-52
View File
@@ -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)));
};
+5 -5
View File
@@ -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