From d28a511070c57b3c6b59580b99d070b2ed3c3e40 Mon Sep 17 00:00:00 2001 From: ezl-keygraph Date: Wed, 11 Feb 2026 00:19:59 +0530 Subject: [PATCH] feat: upgrade claude-agent-sdk to 0.2.38 and adapt to new SDK types (#113) * feat: upgrade claude-agent-sdk to 0.2.38 and adapt to new SDK types - Bump @anthropic-ai/claude-agent-sdk from 0.1.x to 0.2.38 (both root and mcp-server) - Bump zod from 3.x to 4.x (SDK peer dependency) - Add allowDangerouslySkipPermissions to query options (required for bypassPermissions) - Suppress new SDK message types (tool_progress, tool_use_summary, auth_status) - Use structured error field on assistant messages instead of text-sniffing - Add stop_reason to result message handling for diagnostics - Add SDKAssistantMessageError type matching SDK's string literal union * chore: remove CLAUDE_CODE_MAX_OUTPUT_TOKENS from all config and docs --- .env.example | 3 -- CLAUDE.md | 1 - README.md | 2 - docker-compose.yml | 1 - mcp-server/package-lock.json | 97 +++++++++++++++++++++++++++++++---- mcp-server/package.json | 4 +- package-lock.json | 99 +++++++++++++++++++++++++++++++----- package.json | 4 +- src/ai/claude-executor.ts | 1 + src/ai/message-handlers.ts | 87 ++++++++++++++++++++++++++++++- src/ai/types.ts | 12 +++++ 11 files changed, 278 insertions(+), 33 deletions(-) diff --git a/.env.example b/.env.example index b9aab98..b0d1875 100644 --- a/.env.example +++ b/.env.example @@ -1,9 +1,6 @@ # Shannon Environment Configuration # Copy this file to .env and fill in your credentials -# Recommended output token configuration for larger tool outputs -CLAUDE_CODE_MAX_OUTPUT_TOKENS=64000 - # ============================================================================= # OPTION 1: Direct Anthropic (default, no router) # ============================================================================= diff --git a/CLAUDE.md b/CLAUDE.md index 096c0bb..1ce8d2c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,7 +18,6 @@ This is an AI-powered penetration testing agent designed for defensive security cp .env.example .env # Edit .env: # ANTHROPIC_API_KEY=your-key -# CLAUDE_CODE_MAX_OUTPUT_TOKENS=64000 # Prevents token limits during long reports # Start a pentest workflow ./shannon start URL= REPO= diff --git a/README.md b/README.md index 89e2bfa..f8dbea0 100644 --- a/README.md +++ b/README.md @@ -119,12 +119,10 @@ cd shannon # Option A: Export environment variables export ANTHROPIC_API_KEY="your-api-key" # or CLAUDE_CODE_OAUTH_TOKEN -export CLAUDE_CODE_MAX_OUTPUT_TOKENS=64000 # recommended # Option B: Create a .env file cat > .env << 'EOF' ANTHROPIC_API_KEY=your-api-key -CLAUDE_CODE_MAX_OUTPUT_TOKENS=64000 EOF # 3. Run a pentest diff --git a/docker-compose.yml b/docker-compose.yml index e54ba1f..b68c0b3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,7 +24,6 @@ services: - ANTHROPIC_AUTH_TOKEN=${ANTHROPIC_AUTH_TOKEN:-} # Auth token for router - ROUTER_DEFAULT=${ROUTER_DEFAULT:-} # Model name when using router (e.g., "gemini,gemini-2.5-pro") - CLAUDE_CODE_OAUTH_TOKEN=${CLAUDE_CODE_OAUTH_TOKEN:-} - - CLAUDE_CODE_MAX_OUTPUT_TOKENS=${CLAUDE_CODE_MAX_OUTPUT_TOKENS:-64000} depends_on: temporal: condition: service_healthy diff --git a/mcp-server/package-lock.json b/mcp-server/package-lock.json index 1374377..fcc81c7 100644 --- a/mcp-server/package-lock.json +++ b/mcp-server/package-lock.json @@ -8,8 +8,8 @@ "name": "@shannon/mcp-server", "version": "1.0.0", "dependencies": { - "@anthropic-ai/claude-agent-sdk": "^0.1.0", - "zod": "^3.22.4" + "@anthropic-ai/claude-agent-sdk": "^0.2.38", + "zod": "^4.3.6" }, "devDependencies": { "@types/node": "^25.0.3", @@ -17,9 +17,9 @@ } }, "node_modules/@anthropic-ai/claude-agent-sdk": { - "version": "0.1.25", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.1.25.tgz", - "integrity": "sha512-qwuydYaA3uamz4ivDzYXfL2PBjGwc0+beeIyo3nvtZQOtFLjH7xPdBK2w3+9KnB3L6V7VooAMdTXPpQyxCwcOg==", + "version": "0.2.38", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.2.38.tgz", + "integrity": "sha512-U1vpf3rlSkw1qUlzC6CBibBA30ouQnla9JnuqYFLQ2zBb1U2NUCXIElrnV7RwWrI5e9ZKCHgR+1uaCwROONo7w==", "license": "SEE LICENSE IN README.md", "engines": { "node": ">=18.0.0" @@ -30,10 +30,12 @@ "@img/sharp-linux-arm": "^0.33.5", "@img/sharp-linux-arm64": "^0.33.5", "@img/sharp-linux-x64": "^0.33.5", + "@img/sharp-linuxmusl-arm64": "^0.33.5", + "@img/sharp-linuxmusl-x64": "^0.33.5", "@img/sharp-win32-x64": "^0.33.5" }, "peerDependencies": { - "zod": "^3.24.1" + "zod": "^4.0.0" } }, "node_modules/@img/sharp-darwin-arm64": { @@ -160,6 +162,38 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@img/sharp-linux-arm": { "version": "0.33.5", "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", @@ -226,6 +260,50 @@ "@img/sharp-libvips-linux-x64": "1.0.4" } }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, "node_modules/@img/sharp-win32-x64": { "version": "0.33.5", "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", @@ -277,10 +355,11 @@ "license": "MIT" }, "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/mcp-server/package.json b/mcp-server/package.json index f3e8ac3..9faa35a 100644 --- a/mcp-server/package.json +++ b/mcp-server/package.json @@ -8,8 +8,8 @@ "clean": "rm -rf dist" }, "dependencies": { - "@anthropic-ai/claude-agent-sdk": "^0.1.0", - "zod": "^3.22.4" + "@anthropic-ai/claude-agent-sdk": "^0.2.38", + "zod": "^4.3.6" }, "devDependencies": { "@types/node": "^25.0.3", diff --git a/package-lock.json b/package-lock.json index 63e6d71..b309592 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "shannon", "version": "1.0.0", "dependencies": { - "@anthropic-ai/claude-agent-sdk": "^0.1.0", + "@anthropic-ai/claude-agent-sdk": "^0.2.38", "@temporalio/activity": "^1.11.0", "@temporalio/client": "^1.11.0", "@temporalio/worker": "^1.11.0", @@ -21,12 +21,9 @@ "figlet": "^1.9.3", "gradient-string": "^3.0.0", "js-yaml": "^4.1.0", - "zod": "^3.22.4", + "zod": "^4.3.6", "zx": "^8.0.0" }, - "bin": { - "shannon": "dist/shannon.js" - }, "devDependencies": { "@types/js-yaml": "^4.0.9", "@types/node": "^25.0.3", @@ -34,9 +31,9 @@ } }, "node_modules/@anthropic-ai/claude-agent-sdk": { - "version": "0.1.25", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.1.25.tgz", - "integrity": "sha512-qwuydYaA3uamz4ivDzYXfL2PBjGwc0+beeIyo3nvtZQOtFLjH7xPdBK2w3+9KnB3L6V7VooAMdTXPpQyxCwcOg==", + "version": "0.2.38", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.2.38.tgz", + "integrity": "sha512-U1vpf3rlSkw1qUlzC6CBibBA30ouQnla9JnuqYFLQ2zBb1U2NUCXIElrnV7RwWrI5e9ZKCHgR+1uaCwROONo7w==", "license": "SEE LICENSE IN README.md", "engines": { "node": ">=18.0.0" @@ -47,10 +44,12 @@ "@img/sharp-linux-arm": "^0.33.5", "@img/sharp-linux-arm64": "^0.33.5", "@img/sharp-linux-x64": "^0.33.5", + "@img/sharp-linuxmusl-arm64": "^0.33.5", + "@img/sharp-linuxmusl-x64": "^0.33.5", "@img/sharp-win32-x64": "^0.33.5" }, "peerDependencies": { - "zod": "^3.24.1" + "zod": "^4.0.0" } }, "node_modules/@grpc/grpc-js": { @@ -208,6 +207,38 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@img/sharp-linux-arm": { "version": "0.33.5", "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", @@ -274,6 +305,50 @@ "@img/sharp-libvips-linux-x64": "1.0.4" } }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, "node_modules/@img/sharp-win32-x64": { "version": "0.33.5", "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", @@ -2544,9 +2619,9 @@ } }, "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", "peer": true, "funding": { diff --git a/package.json b/package.json index 0d3cb26..c38b5dd 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "temporal:query": "node dist/temporal/query.js" }, "dependencies": { - "@anthropic-ai/claude-agent-sdk": "^0.1.0", + "@anthropic-ai/claude-agent-sdk": "^0.2.38", "@temporalio/activity": "^1.11.0", "@temporalio/client": "^1.11.0", "@temporalio/worker": "^1.11.0", @@ -24,7 +24,7 @@ "figlet": "^1.9.3", "gradient-string": "^3.0.0", "js-yaml": "^4.1.0", - "zod": "^3.22.4", + "zod": "^4.3.6", "zx": "^8.0.0" }, "devDependencies": { diff --git a/src/ai/claude-executor.ts b/src/ai/claude-executor.ts index bd194e2..8bf5b8c 100644 --- a/src/ai/claude-executor.ts +++ b/src/ai/claude-executor.ts @@ -223,6 +223,7 @@ export async function runClaudePrompt( maxTurns: 10_000, cwd: sourceDir, permissionMode: 'bypassPermissions' as const, + allowDangerouslySkipPermissions: true, mcpServers, }; diff --git a/src/ai/message-handlers.ts b/src/ai/message-handlers.ts index b57ffa3..3dcede5 100644 --- a/src/ai/message-handlers.ts +++ b/src/ai/message-handlers.ts @@ -22,6 +22,7 @@ import type { AuditLogger } from './audit-logger.js'; import type { ProgressManager } from './progress-manager.js'; import type { AssistantMessage, + SDKAssistantMessageError, ResultMessage, ToolUseMessage, ToolResultMessage, @@ -100,13 +101,86 @@ export function detectApiError(content: string): ApiErrorDetection { return { detected: false }; } +// Maps SDK structured error types to our error handling. +function handleStructuredError( + errorType: SDKAssistantMessageError, + content: string +): ApiErrorDetection { + switch (errorType) { + case 'billing_error': + return { + detected: true, + shouldThrow: new PentestError( + `Billing error (structured): ${content.slice(0, 100)}`, + 'billing', + true // Retryable with backoff + ), + }; + case 'rate_limit': + return { + detected: true, + shouldThrow: new PentestError( + `Rate limit hit (structured): ${content.slice(0, 100)}`, + 'network', + true // Retryable with backoff + ), + }; + case 'authentication_failed': + return { + detected: true, + shouldThrow: new PentestError( + `Authentication failed: ${content.slice(0, 100)}`, + 'config', + false // Not retryable - needs API key fix + ), + }; + case 'server_error': + return { + detected: true, + shouldThrow: new PentestError( + `Server error (structured): ${content.slice(0, 100)}`, + 'network', + true // Retryable + ), + }; + case 'invalid_request': + return { + detected: true, + shouldThrow: new PentestError( + `Invalid request: ${content.slice(0, 100)}`, + 'config', + false // Not retryable - needs code fix + ), + }; + case 'max_output_tokens': + return { + detected: true, + shouldThrow: new PentestError( + `Max output tokens reached: ${content.slice(0, 100)}`, + 'billing', + true // Retryable - may succeed with different content + ), + }; + case 'unknown': + default: + return { detected: true }; + } +} + export function handleAssistantMessage( message: AssistantMessage, turnCount: number ): AssistantResult { const content = extractMessageContent(message); const cleanedContent = filterJsonToolCalls(content); - const errorDetection = detectApiError(content); + + // Prefer structured error field from SDK, fall back to text-sniffing + let errorDetection: ApiErrorDetection; + if (message.error) { + errorDetection = handleStructuredError(message.error, content); + } else { + errorDetection = detectApiError(content); + } const result: AssistantResult = { content, @@ -141,6 +215,14 @@ export function handleResultMessage(message: ResultMessage): ResultData { result.subtype = message.subtype; } + // Capture stop_reason for diagnostics (helps debug early stops, budget exceeded, etc.) + if (message.stop_reason !== undefined) { + result.stop_reason = message.stop_reason; + if (message.stop_reason && message.stop_reason !== 'end_turn') { + console.log(chalk.yellow(` Stop reason: ${message.stop_reason}`)); + } + } + return result; } @@ -247,6 +329,9 @@ export async function dispatchMessage( } case 'user': + case 'tool_progress': + case 'tool_use_summary': + case 'auth_status': return { type: 'continue' }; case 'tool_use': { diff --git a/src/ai/types.ts b/src/ai/types.ts index b754d0c..f742dd3 100644 --- a/src/ai/types.ts +++ b/src/ai/types.ts @@ -46,6 +46,7 @@ export interface ResultData { cost: number; duration_ms: number; subtype?: string; + stop_reason?: string | null; permissionDenials: number; } @@ -66,8 +67,18 @@ export interface ContentBlock { text?: string; } +export type SDKAssistantMessageError = + | 'authentication_failed' + | 'billing_error' + | 'rate_limit' + | 'invalid_request' + | 'server_error' + | 'max_output_tokens' + | 'unknown'; + export interface AssistantMessage { type: 'assistant'; + error?: SDKAssistantMessageError; message: { content: ContentBlock[] | string; }; @@ -79,6 +90,7 @@ export interface ResultMessage { total_cost_usd?: number; duration_ms?: number; subtype?: string; + stop_reason?: string | null; permission_denials?: unknown[]; }