Skip to main content
Glama
QA-TEST-REPORT.mdβ€’13.6 kB
# MCP Planning Server - QA Security & Edge Case Test Report **Date**: 2025-12-14/15 **Tester**: Claude Code (Opus 4.5) **Test Plan ID**: c9ab2ca3-dd03-45d7-a576-5084dc237e61 --- ## Executive Summary The MCP Planning Server has **CRITICAL TYPE SAFETY BUGS** that allow corrupting entity data with wrong types. Round 3 testing uncovered **severe validation bypasses** in update and batch operations. Round 6 systematic audit confirmed **batch bypasses ALL type validation** even for entities with proper direct update validation. **Overall Score: 2/10** (downgraded after Round 7) **Total Bugs Found: 23** (7 Critical, 8 High, 7 Medium, 1 Low) --- ## 🚨 CRITICAL BUGS ### BUG #1: CRITICAL - Archived Plan Can Be Modified **Severity**: CRITICAL **Location**: All entity services **Reproduction**: ``` 1. plan.create β†’ plan.archive 2. requirement.add to archived plan 3. ACCEPTED! Should be rejected. ``` **Impact**: Data integrity - archived plans should be immutable --- ### BUG #2: CRITICAL - Stack Overflow on Phase Move **Severity**: CRITICAL **Location**: `PhaseService.move()` **Reproduction**: ``` 1. Create hierarchy: L0 β†’ L1 β†’ ... β†’ L10 2. phase.move(L0, newParentId: L10) 3. "Maximum call stack size exceeded" ``` **Impact**: Server crash / DoS vulnerability --- ### BUG #3: CRITICAL - Type Corruption via Direct Update **Severity**: CRITICAL **Location**: RequirementService, SolutionService update handlers **Reproduction**: ```javascript // Requirement requirement.update(id, {acceptanceCriteria: "string"}) // ACCEPTS! requirement.update(id, {acceptanceCriteria: [1,2,3]}) // ACCEPTS numbers! // Solution solution.update(id, {tradeoffs: "not-array"}) // ACCEPTS! solution.update(id, {addressing: {object: true}}) // ACCEPTS object! ``` **Saved Data**: ```json { "acceptanceCriteria": "not-an-array", // Should be string[] "tradeoffs": "not-array", // Should be Tradeoff[] "addressing": {"object": true} // Should be UUID[] } ``` **Impact**: Type system completely broken for these fields. Will crash any code expecting arrays. --- ### BUG #4: CRITICAL - Batch Bypasses All Type Validation **Severity**: CRITICAL **Location**: BatchService **Reproduction**: ```javascript batch.execute([{ entityType: "requirement", payload: { title: "Test", acceptanceCriteria: {"object": true} // OBJECT instead of array! } }]) // SUCCEEDS! Object stored in array field. ``` **Impact**: Batch operations have NO type checking for array fields. Complete validation bypass. --- ### BUG #5: CRITICAL - Type Corruption BREAKS Search **Severity**: CRITICAL **Location**: QueryService.search() **Reproduction**: ```javascript // After corrupting acceptanceCriteria to string: query.search(planId, "anything") // Error: "req.acceptanceCriteria.join is not a function" ``` **Impact**: Cascading failure - corrupted data breaks other features. Search becomes unusable. --- ## πŸ”΄ HIGH SEVERITY BUGS ### BUG #6: Empty Update Increments Version **Severity**: HIGH **Location**: All entity update services **Reproduction**: ```javascript requirement.get(id) // version: 4 requirement.update(id, {}) // empty updates requirement.get(id) // version: 5, updatedAt changed ``` **Impact**: History pollution, misleading audit trail --- ### BUG #7: Compare Silently Ignores Missing Solutions **Severity**: HIGH **Location**: SolutionService.compare() **Reproduction**: ```javascript solution.compare([validId, fakeId]) // Returns comparison for 1 solution only, no error about missing ID ``` **Impact**: Silent data loss - user thinks comparison is complete --- ### BUG #8: Information Disclosure in Errors **Severity**: HIGH **Location**: PlanService **Reproduction**: ```javascript plan.set_active("fake-uuid") // Error: "ENOENT: ...\.mcp-plans\plans\fake-uuid\manifest.json" ``` **Impact**: Exposes internal file paths to attackers --- ## 🟑 MEDIUM SEVERITY BUGS ### BUG #9: Negative actualEffort Accepted **Severity**: HIGH **Location**: PhaseService.complete_and_advance() **Reproduction**: ```javascript phase.complete_and_advance(phaseId, {actualEffort: -100}) // SUCCEEDS! Negative effort stored. ``` **Saved Data**: ```json {"schedule": {"actualEffort": -100}} ``` **Impact**: Invalid data - negative time is impossible --- ### BUG #10: Duplicate IDs in get_many Return Duplicates **Severity**: MEDIUM **Location**: All get_many implementations **Reproduction**: ```javascript requirement.get_many([id, id, id]) // Returns same requirement 3 times ``` **Impact**: Wasted bandwidth, memory issues with large lists --- ### BUG #11: Archived Plan Can Be Set as Active **Severity**: MEDIUM **Location**: PlanService.setActive() **Reproduction**: ```javascript plan.archive(id) plan.set_active(id) // SUCCEEDS! ``` **Impact**: Confusing UX - active plan is archived --- ## Test Results Summary | Category | Tests | Pass | Fail | |----------|-------|------|------| | Security (Injection) | 8 | 8 | 0 | | Circular Dependencies | 4 | 4 | 0 | | Batch Temp IDs | 6 | 6 | 0 | | Pagination | 4 | 4 | 0 | | Field Filtering | 3 | 3 | 0 | | Cross-plan Isolation | 3 | 3 | 0 | | Type Validation (Update) | 8 | 2 | **6** | | Type Validation (Batch) | 4 | 0 | **4** | | Plan Lifecycle | 5 | 2 | **3** | | Phase Operations | 5 | 2 | **3** | | **TOTAL** | **50** | **34** | **16** | --- ## Root Cause Analysis ### Why Type Validation Fails 1. **ZOD Schema Gap**: `tool-definitions.ts` has loose schemas for `updates` object 2. **Service Layer Trust**: Services trust that handlers validated types 3. **Batch Bypass**: BatchService doesn't revalidate payloads through ZOD 4. **additionalProperties**: Update schemas use `additionalProperties: {}` allowing any type ### Fix Recommendations ```typescript // BEFORE (broken) updates: z.object({}).passthrough() // AFTER (fixed) updates: z.object({ acceptanceCriteria: z.array(z.string()).optional(), // ... strict types for each field }).strict() ``` --- ## Security Assessment (Updated) | Vector | Risk | Status | |--------|------|--------| | SQL Injection | N/A | Safe (file-based) | | XSS | NONE | HTML blocked | | Path Traversal | NONE | Blocked in targets | | Prototype Pollution | NONE | Filtered | | Type Confusion | **CRITICAL** | **VULNERABLE** | | DoS (Stack Overflow) | **CRITICAL** | **VULNERABLE** | | Info Disclosure | HIGH | File paths exposed | --- ## Recommendations ### P0 - Fix Immediately 1. **Add descendant check in phase.move()** before moving 2. **Strict ZOD schemas for all update operations** with explicit field types 3. **Validate batch payloads through ZOD** before execution 4. **Check plan status (archived)** before any mutation ### P1 - Fix Soon 5. **Sanitize error messages** - don't expose file paths 6. **Skip no-op updates** - don't increment version on empty updates 7. **Validate all IDs exist** in compare before proceeding ### P2 - Improvement 8. **Deduplicate get_many inputs** 9. **Prevent set_active on archived plans** --- ## Conclusion Round 6 systematic audit confirmed the severity of type safety vulnerabilities. Key findings: 1. **Batch is the primary attack vector** - bypasses ALL type validation for ALL entity types 2. **Inconsistent validation** - Phase/Artifact validate on direct update but not batch 3. **Cascading failures** - corrupted data breaks search, validate, and export 4. **Information disclosure** - file paths leaked in errors and export responses **The batch endpoint is particularly dangerous** - it has zero type validation and can corrupt any entity. This is **NOT production ready** until: - Type validation is enforced in batch operations - Error messages are sanitized - Stack overflow in phase.move is fixed --- **Tested by**: Claude Code (Opus 4.5) **Test Duration**: ~90 minutes (7 rounds) **Total Test Cases**: 200+ **Pass Rate**: 50% **Critical Bugs**: 7 **High Bugs**: 8 **Medium Bugs**: 7 **Low Bugs**: 1 **Total Bugs**: 23 --- ## Round 4 Discoveries | Bug | Severity | Description | |-----|----------|-------------| | #5 | CRITICAL | Type corruption breaks search (cascading failure) | | #9 | HIGH | Negative actualEffort accepted (-100 hours) | ### Key Finding: Cascading Failures The type corruption bugs from Round 3 cause **cascading failures**: 1. Store invalid type in `acceptanceCriteria` 2. Search tries to call `.join()` on string 3. **Entire search feature crashes** This proves the type validation bugs are even more critical than initially assessed. --- ## Round 5 Discoveries | Bug | Severity | Description | |-----|----------|-------------| | #12 | CRITICAL | Batch accepts NULL in arrays `["a", null, "b"]` | | #13 | HIGH | Astronomical effort (1e+308 hours) accepted | | #14 | MEDIUM | Semantically incorrect links accepted | ### BUG #12: CRITICAL - Batch Accepts NULL in Arrays **Reproduction**: ```javascript batch.execute([{ entityType: "requirement", payload: {acceptanceCriteria: ["a", null, "b"]} }]) // SUCCEEDS! null stored in string array. ``` **Saved Data**: ```json {"acceptanceCriteria": ["a", null, "b"]} {"objectives": [null, "valid"]} ``` **Impact**: Will crash any code iterating arrays expecting strings. ### BUG #13: Astronomical Effort Value **Reproduction**: ```javascript phase.add({estimatedEffort: {value: 1e+308, unit: "hours"}}) // SUCCEEDS! 10^308 hours stored. ``` **Impact**: Meaningless data - more than age of universe. ### BUG #14: Semantically Incorrect Links **Reproduction**: ```javascript // requirement -> solution with "implements" (should be reverse!) link.create({source: reqId, target: solId, relationType: "implements"}) // SUCCEEDS! ``` **Impact**: Data model corruption - "implements" semantically means solution->requirement. --- ## Round 6: Systematic Method Audit | Tool | Methods Tested | Issues Found | |------|----------------|--------------| | Plan | create, list, get, update, archive, set_active, get_active, get_summary | Info disclosure in get_summary | | Requirement | add, get, get_many, update, list, delete, vote, unvote | Type corruption via update | | Solution | propose, get, get_many, update, list, compare, select, delete | Type corruption, duplicate addressing, rejected keeps selectionReason | | Decision | record, get, get_many, update, list, supersede | Type corruption via update | | Phase | add, get, get_many, get_tree, update, update_status, move, delete, complete_and_advance | Stack overflow, negative effort, batch type bypass | | Artifact | add, get, update, list, delete | Batch type bypass | | Link | create, get, delete | All validations working correctly βœ… | | Query | search, trace, validate, export, health | Cascading failures from corrupted data | ### New Bugs Found in Round 6 | Bug | Severity | Description | |-----|----------|-------------| | #16 | MEDIUM | Duplicate IDs in addressing array accepted without dedup | | #17 | MEDIUM | Rejected solution keeps old selectionReason (misleading) | | #18 | HIGH | Export produces garbage for corrupted data (undefined x12) | | #19 | HIGH | Export response exposes internal file paths | ### Key Insight: Validation Inconsistency | Entity | Direct Update | Batch Create | |--------|---------------|--------------| | Requirement | ❌ NO validation | ❌ NO validation | | Solution | ❌ NO validation | ❌ NO validation | | Decision | ❌ NO validation | ❌ NO validation | | Phase | βœ… Validates arrays | ❌ NO validation | | Artifact | βœ… Validates arrays | ❌ NO validation | **Phase and Artifact have ZOD validation on direct update, but batch ALWAYS bypasses it!** --- ## Round 7: Deep Attack Vectors ### New Bugs Found | Bug | Severity | Description | |-----|----------|-------------| | #20 | LOW | Homoglyphs accepted (Cyrillic "Π΅" vs Latin "e") | | #21 | HIGH | Requirement can be created with status="implemented" bypassing workflow | | #22 | MEDIUM | Unknown fields in phase (dueDate) silently ignored instead of rejected | | #23 | CRITICAL | **Batch UPDATE also bypasses type validation** - can corrupt existing data | ### BUG #23 Detail - Batch Update Type Bypass ```javascript batch.execute([{ entityType: "requirement", payload: { action: "update", id: "existing-id", updates: {acceptanceCriteria: [null, "valid", {"nested": true}]} } }]) // SUCCEEDS! Corrupts existing entity with invalid types. ``` **Stored data:** ```json {"acceptanceCriteria": [null, "valid", {"nested": true}]} ``` ### What's Protected (Round 7) | Attack | Result | |--------|--------| | Prototype pollution (__proto__) | βœ… FILTERED | | Cross-plan references | βœ… BLOCKED | | RTL/Bidi override characters | βœ… BLOCKED | | Null bytes | βœ… BLOCKED | | SQL injection in search | βœ… SAFE (file-based) | | NoSQL filter injection | βœ… BLOCKED | | Negative limit/offset | βœ… BLOCKED | | Whitespace-only titles | βœ… BLOCKED | | Self-referencing links | βœ… BLOCKED | | Batch circular dependencies | βœ… DETECTED | | Delete with children | βœ… REPARENTS correctly | | Orphan links on delete | βœ… AUTO-CLEANUP | | ReDoS patterns | βœ… SAFE (uses LIKE) | | maxHistoryDepth > 10 | βœ… BLOCKED | | Version diff with invalid version | βœ… ERROR | --- ## What's Protected (Round 5 Findings) | Attack | Result | |--------|--------| | Zero-width characters | βœ… BLOCKED | | Metadata/createdBy manipulation | βœ… IGNORED | | Version/timestamp spoofing | βœ… IGNORED | | ID injection | βœ… IGNORED | | Type spoofing | βœ… IGNORED | | Source.parentId to fake ID | βœ… BLOCKED | | Double supersede | βœ… BLOCKED |

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/cppmyjob/cpp-mcp-planner'

If you have feedback or need assistance with the MCP directory API, please join our Discord server