T05 · Unauthorized Access and Privilege Escalation
- Location
scripts/mcp_server.py:447- Finding
MCP Memory Operations Bypass HMAC Authorization and Namespace Isolation
- Content
View full analysis
Vulnerability Details
File Location:
scripts/mcp_server.py:177-187,scripts/mcp_server.py:326-419,scripts/mcp_server.py:447-460,scripts/store.py:273-305,scripts/memory_access.py:174-203
Vulnerability Type: Missing authorization and namespace enforcement
Risk Level: HighComplete Code Snippets
The deposit handler accepts a caller-selected namespace and writes to it without validating a signed manifest:
python def _handle_zwjh_deposit(arguments: dict) -> dict: """Process a zwjh_deposit tool call.""" text = arguments.get("text", "") if not text: return _tool_result("Error: missing text parameter", is_error=True) source = arguments.get("source", "conversation") namespace = arguments.get("namespace", "public") r = deposit.deposit_text(text, source=source, namespace=namespace)All MCP tool calls are dispatched directly to their handlers without an authorization check:
python def _handle_tools_call(request_id: Any, params: dict) -> dict: """Process a tools/call request.""" tool_name = params.get("name", "") arguments = params.get("arguments", {}) handler = TOOL_HANDLERS.get(tool_name) if not handler: return _rpc_error(request_id, -32601, "Unknown tool: %s" % tool_name) try: result = handler(arguments) return _rpc_result(request_id, result) except Exception as e: return _rpc_error(request_id, ERR_INTERNAL, "%s" % str(e))The export handler reads memories without a namespace restriction:
python rows = store.list_memories( day_from=day_from, day_to=day_to, limit=100000, offset=0, )The underlying storage functions likewise query every namespace:
python def list_memories(day_from: str | None = None, day_to: str | None = None, limit: int = 200, offset: int = 0) -> list[dict]: conn = get_conn() sql = "SELECT * FROM memories WHERE 1=1" args: list = [] if day_from: ...[truncated 3638 chars]- Remediation
View remediation
Remediation Suggestions
- Add a signed
manifestand explicit namespace to every namespace-sensitive MCP tool schema. - Before dispatching an operation, call
memory_access.check_namespace_permission()with the required permission:readfor query, timeline, graph lookup, health details, and export.writefor deposit.adminfor forget, restore, or other management operations.
- Derive the effective namespace from the validated manifest rather than trusting a caller-provided namespace.
- Add namespace parameters to storage and retrieval functions and enforce them directly in SQL:
sql SELECT * FROM memories WHERE namespace = ? - Ensure semantic, keyword, time, archive, deduplication, and export queries all apply the same namespace predicate.
- Include the namespace in exact-duplicate and near-duplicate checks so content in one namespace cannot affect records in another.
- Apply namespace and permission checks to archive records and restore operations.
- Add negative tests proving that:
- Missing manifests cannot write or delete.
- Public manifests cannot access private namespaces.
- A manifest for one private namespace cannot access another.
- Invalid, expired, or modified manifests are rejected.
- Add a signed
