SSRF Prevention for MCP HTTP Handlers - #8
Conversation
- Added URL validation to testHttpConnection() to prevent SSRF attacks - Added URL validation to testStreamableHttpConnection() for consistency - Follows same defense-in-depth pattern as checkHttpHealth() and checkStreamableHttpHealth() - Validates URLs before making any network requests to MCP servers
…ers.test - Add comprehensive tests for URL security validation - Test protocol validation (http/https only) - Test embedded credentials blocking - Test localhost allowance (localhost, 127.0.0.1, ::1) - Test private IP blocking (10.0.0.0/8, 169.254.0.0/16, 192.168.0.0/16, 172.16.0.0/12) - Test public IP allowance - Test invalid URL handling - Test edge cases (ports, paths, query params, fragments, IPv6) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add local subnet discovery using os.networkInterfaces() to allow MCP servers on the same LAN while maintaining SSRF protection: - Add ipToInt(), getLocalSubnets(), isInLocalSubnet() helper functions - Modify isUrlAllowed() to allow private IPs on local subnets - Always block 169.254.x.x (cloud metadata) regardless of subnet - Block 0.0.0.0 as invalid destination address - Fix non-null assertion lint warnings in command spawn functions - Add comprehensive tests for subnet discovery and validation This enables users with MCP servers on their local network (e.g., 192.168.1.x) to connect while still blocking access to remote private networks and cloud metadata endpoints. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
PR Compliance Guide 🔍(Compliance updated until commit 4662d35)Below is a summary of compliance checks for this PR:
Compliance status legend🟢 - Fully Compliant🟡 - Partial Compliant 🔴 - Not Compliant ⚪ - Requires Further Human Verification 🏷️ - Compliance label Previous compliance checksCompliance check up to commit f0c783a
|
||||||||||||||||||||||||||||||||||||||||||||||||||
PR Code Suggestions ✨Latest suggestions up to 974feee
Previous suggestions✅ Suggestions up to commit f0c783a
|
||||||||||||||||||||||||||||||
Validate that IP octets are in the valid range (0-255) and return -1 for malformed IP addresses. This prevents potential edge cases where overflowed values could cause unexpected subnet matching behavior. - ipToInt() now returns -1 for invalid IPs (out of range octets, wrong number of parts, non-numeric values) - isInLocalSubnet() rejects invalid IPs by checking for -1 - Added comprehensive tests for malformed IP handling Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace custom regex and manual octet validation with Node.js net.isIPv4() for more robust IP address validation. This automatically rejects malformed IPs like 999.999.999.999 and parseInt-permissive forms like "1e2.0.0.1". Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…yMik90#1338) (AndyMik90#1575) * feat: add backend task event protocol * fix: harden spec_runner project detection * feat: parse task events and track sequences * feat: add xstate task machine * feat: wire task events into state manager * refactor: centralize status handling in state manager * feat: hydrate task state and propagate reviewReason * auto-claude: subtask-1-1 - Create card_data.txt file with literal string 'card data' Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> * fix: skip stuck detection for QA phases to prevent race conditions Added qa_review and qa_fixing to the stuck detection skip list in both TaskCard.tsx and useTaskDetail.ts. When the process exits unexpectedly during QA phases, XState handles transitioning to error state. Skipping stuck detection for these phases avoids race conditions where the stuck check fires before the status update IPC reaches the renderer. Also added unit tests for task-machine (35 tests) and task-state-manager (20 tests), plus XSTATE_MIGRATION_SUMMARY.md documenting the migration. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: use XState as source of truth instead of stale cache - Add getCurrentState() and isInPlanReview() methods to TaskStateManager - Fix TASK_START handler to check XState actor state before falling back to task data - Fix handleManualStatusChange to use XState state for determining correct event - Prevents wrong event being sent when plan approval happens with stale cached data - Add debug logging throughout state transitions for troubleshooting The root cause was that when approving a plan, the UI called startTask() which used cached task data (3-second TTL) to determine which XState event to send. If the cache was stale, it would send USER_RESUMED instead of PLAN_APPROVED, causing the task to transition incorrectly. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: prevent plan updates from overwriting XState-controlled status When TASK_PROGRESS events arrived with stale plan data containing status: 'in_progress', updateTaskFromPlan was overwriting the correct XState-set status (e.g., 'ai_review'), causing tasks to jump back to the wrong Kanban column. XState is now the sole source of truth for task status. Plan updates only update subtasks, title, and other non-status fields. Status changes only come through TASK_STATUS_CHANGE events emitted by XState. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: remove AndyMik90#1585 code from PR Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: ruff lint - remove unnecessary string annotation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: biome lint and ruff format fixes - Wrap case 'in_progress' block with braces in task-state-manager.ts - Apply ruff format to 5 Python files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: resolve flaky subprocess-spawn test on Windows CI The 'should track running tasks' test was failing intermittently on Windows CI because both tasks share the same mockProcess, and the timing of exit event handlers could vary between environments. Changes: - Emit exit events twice to ensure both handlers receive them - Use Promise.allSettled to wait for both tasks - Add 100ms delay for event handlers to complete on slower CI Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address PR AndyMik90#1575 review findings and stuck detection false positives - Fix dual status emission in worktree handlers (#1, HIGH): route merge/discard status changes through TaskStateManager instead of direct IPC emission. Add human_review case to handleManualStatusChange. - Extract duplicate phaseMap to shared XSTATE_TO_PHASE constant (#6, LOW) - Add --force flag in spec_runner.py when chaining to run.py after auto-approved specs to prevent BUILD BLOCKED hash mismatch errors - Guard duplicate CODING_STARTED emission in coder.py (#8, MEDIUM): skip second emit when just_transitioned_from_planning is True - Simplify stuck detection to 60s catastrophic-only check: XState handles all normal process-exit transitions via PROCESS_EXITED events. Remove phase-skip logic, visibility handler, and 5s/30s timers. - Record task activity on status changes and log events (not just execution progress) to prevent false positive stuck detection - Add tests for activity recording and human_review manual status change Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(lint): ruff format spec_runner.py long lines Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(test): resolve flaky subprocess-spawn test on Windows CI Wait for spawn promises to fully resolve before emitting exit events, ensuring exit handlers are attached. A single setImmediate was insufficient on Windows CI where async operations (getAPIProfileEnv, getRecoveryCoordinator) between addProcess and .on('exit') take longer. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address PR review findings for XState refactor - CMT-001 [HIGH]: Add 'queue' and 'queued' status mappings to statusMap in project-store.ts to prevent task regression from queue to backlog when loading from disk - NEW-003 [MEDIUM]: Integrate clearAllTasks() into TASK_LIST handler's forceRefresh path and update documentation to reflect actual usage - CMT-003 [MEDIUM]: Change fail-open to fail-closed pattern in spec_runner.py - default require_review=True when JSON parsing fails Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address security and quality findings from PR review Security fixes: - NEW-006 [HIGH]: Add path traversal protection in TASK_CREATE and TASK_UPDATE image handlers using path.basename() sanitization and resolved path validation - NEW-005 [MEDIUM]: Add MIME type validation against allowlist in TASK_CREATE and TASK_UPDATE, consistent with TASK_REVIEW Quality fixes: - NEW-004 [LOW]: Add debug logging when context not found during XState state transitions to aid debugging - NEW-REVIEW-003 [MEDIUM]: Preserve lastSequenceByTask during clearAllTasks() to prevent duplicate event processing if backend events arrive during refresh window Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test: update clearAllTasks test to expect preserved sequence tracking The test was expecting sequences to be cleared after clearAllTasks(), but the implementation was changed to preserve lastSequenceByTask to prevent duplicate event processing during the refresh window. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com> Co-authored-by: AndyMik90 <andre@mikalsenutvikling.no> Co-authored-by: Andy <119136210+AndyMik90@users.noreply.github.com>
User description
Add URL validation to prevent Server-Side Request Forgery (SSRF) attacks in MCP HTTP and Streamable HTTP handlers.
PR Type
Enhancement, Tests
Description
Add comprehensive SSRF prevention with URL validation function
Implement local subnet discovery using os.networkInterfaces()
Integrate URL validation into all HTTP health check functions
Add extensive test coverage for URL validation and subnet detection
Diagram Walkthrough
flowchart LR A["HTTP Request"] --> B["isUrlAllowed()"] B --> C{"Protocol<br/>Valid?"} C -->|No| D["Reject"] C -->|Yes| E{"Credentials<br/>Present?"} E -->|Yes| D E -->|No| F{"Localhost or<br/>Public IP?"} F -->|Yes| G["Allow"] F -->|No| H{"Private IP in<br/>Local Subnet?"} H -->|Yes| G H -->|No| I{"Cloud Metadata<br/>169.254.x.x?"} I -->|Yes| D I -->|No| D G --> J["Make Request"] D --> K["Return Error"]File Walkthrough
mcp-handlers.ts
Add SSRF prevention and local subnet discovery functionsapps/frontend/src/main/ipc-handlers/mcp-handlers.ts
ipToInt()function to convert IPv4 addresses to 32-bit integersgetLocalSubnets()to discover local network interfaces withcaching
isInLocalSubnet()to check if IP belongs to local subnetsisUrlAllowed()function implementing comprehensive SSRF preventioncheckHttpHealth()andcheckStreamableHttpHealth()testHttpConnection()andtestStreamableHttpConnection()checkCommandHealth()andtestCommandConnection()mcp-handlers.test.ts
Add comprehensive SSRF prevention and subnet detection testsapps/frontend/src/main/ipc-handlers/tests/mcp-handlers.test.ts
os.networkInterfaces()for deterministic subnet testingisUrlAllowed()covering protocols, credentials,IP ranges
ipToInt()with edge cases and boundary valuesgetLocalSubnets()including caching behaviorisInLocalSubnet()with multiple interfaces and subnetmasks