id: PYSEC-2026-2625 published: "2026-07-13T14:36:41.458247Z" modified: "2026-07-13T16:04:48.960229Z" aliases: - CVE-2026-33010 - GHSA-g9rg-8vq5-mpwm summary: mcp-memory-service's Wildcard CORS with Credentials Enables Cross-Origin Memory Theft details: "### Summary\nWhen the HTTP server is enabled (`MCP_HTTP_ENABLED=true`), the application configures FastAPI's CORSMiddleware with `allow_origins=['*']`, `allow_credentials=True`, `allow_methods=[\"*\"]`, and `allow_headers=[\"*\"]`. The wildcard `Access-Control-Allow-Origin: *` header permits any website to read API responses cross-origin. When combined with anonymous access (`MCP_ALLOW_ANONYMOUS_ACCESS=true`) - the simplest way to get the HTTP dashboard working without OAuth - no credentials are needed, so any malicious website can silently read, modify, and delete all stored memories.\n\n\n### Details\n### Vulnerable Code\n\n**`config.py:546` - Wildcard CORS origin default**\n\n```python\nCORS_ORIGINS = os.getenv('MCP_CORS_ORIGINS', '*').split(',')\n```\n\nThis produces `['*']` by default, allowing any origin.\n\n**`app.py:274-280` - CORSMiddleware configuration**\n\n```python\n# CORS middleware\napp.add_middleware(\n CORSMiddleware,\n allow_origins=CORS_ORIGINS, # ['*'] by default\n allow_credentials=True, # Unnecessary for anonymous access; bad practice\n allow_methods=[\"*\"],\n allow_headers=[\"*\"],\n)\n```\n\n### How the Attack Works\n\nThe wildcard CORS default means every API response includes `Access-Control-Allow-Origin: *`. This tells browsers to allow **any website** to read the response. When combined with anonymous access (no authentication required), the attack is straightforward:\n\n```javascript\n// Running on https://evil.com - reads victim's memories\n// No credentials needed - anonymous access means the API is open\nconst response = await fetch('http://192.168.1.100:8000/api/memories');\nconst memories = await response.json();\n// memories contains every stored memory - passwords, API keys, personal notes\n```\n\nThe browser sends the request, the server responds with `ACAO: *`, and the browser allows the JavaScript to read the response body. No cookies, no auth headers, no credentials of any kind.\n\n**Clarification on `allow_credentials=True`:** The advisory originally stated that Starlette reflects the `Origin` header when `allow_credentials=True` with wildcard origins. Testing with Starlette 0.52.1 shows that **actual responses return `ACAO: *`** (not the reflected origin); only preflight `OPTIONS` responses reflect the origin. Per the Fetch specification, browsers block `ACAO: *` when `credentials: 'include'` is used. However, this is irrelevant to the attack because **anonymous access means no credentials are needed** - a plain `fetch()` without `credentials: 'include'` works, and `ACAO: *` allows it.\n\n### Two Attack Vectors\n\nThis misconfiguration enables two distinct attack paths:\n\n**1. Cross-origin browser attack (CORS - this advisory)**\n- Attacker lures victim to a malicious webpage\n- JavaScript on the page reads/writes the memory service API\n- Works from anywhere on the internet if the victim visits the page\n- The `ACAO: *` header is what allows the browser to expose the response to the attacker's JavaScript\n\n**2. Direct network access (compounding factor)**\n- Attacker on the same network directly calls the API (`curl http://:8000/api/memories`)\n- No CORS involved - CORS is a browser-only restriction\n- Enabled by `0.0.0.0` binding + anonymous access, independent of CORS configuration\n\nThe CORS misconfiguration specifically enables attack vector #1, extending the reach from local network to anyone who can get the victim to click a link.\n\n### Compounding Factors\n\n- **`HTTP_HOST = '0.0.0.0'`** - Binds to all interfaces, exposing the service to the entire network (enables attack vector #2)\n- **`HTTPS_ENABLED = 'false'`** - No TLS by default, allowing passive interception\n- **`MCP_ALLOW_ANONYMOUS_ACCESS`** - When enabled, no authentication is required at all. This is the key enabler: without it, the CORS wildcard alone would not allow data access (the attacker would need to forward valid credentials, which `ACAO: *` blocks)\n- **`allow_credentials=True`** - Bad practice: if a future Starlette version changes to reflect origins (as some CORS implementations do), this would escalate the vulnerability by allowing credential-forwarding attacks against OAuth/API-key users\n- **API key via query parameter** - `api_key` query param is cached in browser history and server logs\n\n### Attack Scenario\n\n1. Victim runs `mcp-memory-service` with HTTP enabled and anonymous access\n2. Victim visits `https://evil.com` which includes JavaScript\n3. JavaScript sends `fetch('http://:8000/api/memories')` (no credentials needed)\n4. Server responds with `Access-Control-Allow-Origin: *`\n5. Browser allows JavaScript to read the response - attacker receives all memories\n6. Attacker's script also calls DELETE/PUT endpoints to modify or destroy memories\n7. Victim sees a normal web page; no indication of the attack\n\n### Root Cause\n\nThe default value of `MCP_CORS_ORIGINS` is `*`, which allows any website to read API responses. This is a permissive default that should be restricted to the expected dashboard origin (typically `localhost`). The `allow_credentials=True` is an additional misconfiguration that doesn't currently enable the attack.\n\n\n### PoC\n```python\nfrom fastapi import FastAPI\nfrom fastapi.middleware.cors import CORSMiddleware\nfrom starlette.testclient import TestClient\n\napp = FastAPI()\napp.add_middleware(\n CORSMiddleware,\n allow_origins=[\"*\"],\n allow_credentials=True,\n allow_methods=[\"*\"],\n allow_headers=[\"*\"],\n)\n\n@app.get(\"/api/memories\")\ndef memories():\n return [{\"content\": \"secret memory data\"}]\n\nclient = TestClient(app)\n\n# Non-credentialed request (how the real attack works with anonymous access)\nresponse = client.get(\"/api/memories\", headers={\"Origin\": \"https://evil.com\"})\nprint(response.headers[\"access-control-allow-origin\"]) # *\nprint(response.json()) # [{\"content\": \"secret memory data\"}]\n# Any website can read this response because ACAO is *\n```\n\n\n### Impact\n- **Complete cross-origin memory access**: Any website can read all stored memories when the victim has the HTTP server running with anonymous access\n- **Memory tampering**: Write/delete endpoints are also accessible cross-origin, allowing memory destruction\n- **Remote attack surface**: Unlike direct network access (which requires LAN proximity), the CORS vector works from anywhere on the internet - the victim just needs to visit a link\n- **Silent exfiltration**: The attack is invisible to the victim; no browser warnings, no popups, no indicators\n\n## Remediation\n\nReplace the wildcard default with an explicit localhost origin:\n\n```python\n# In config.py (safe default)\nCORS_ORIGINS = os.getenv('MCP_CORS_ORIGINS', 'http://localhost:8000,http://127.0.0.1:8000').split(',')\n\n# In app.py - warn on wildcard\nif '*' in CORS_ORIGINS:\n logger.warning(\"Wildcard CORS origin detected. This allows any website to access the API. \"\n \"Set MCP_CORS_ORIGINS to restrict access.\")\n\n# Also: set allow_credentials=False unless specific origins are configured\napp.add_middleware(\n CORSMiddleware,\n allow_origins=CORS_ORIGINS,\n allow_credentials='*' not in CORS_ORIGINS, # Only with explicit origins\n allow_methods=[\"*\"],\n allow_headers=[\"*\"],\n)\n```\n\n## Affected Deployments\nThe vulnerability exists in the Python source code and is not mitigated by any deployment-specific configuration. Docker HTTP mode is the highest-risk deployment because it explicitly binds to `0.0.0.0`, maps the port, and does not override the wildcard CORS default." affected: - package: name: mcp-memory-service ecosystem: PyPI purl: pkg:pypi/mcp-memory-service ranges: - type: ECOSYSTEM events: - introduced: "0" - fixed: 10.25.1 versions: - 10.0.0 - 10.0.1 - 10.0.2 - 10.0.3 - 10.1.0 - 10.1.1 - 10.1.2 - 10.10.0 - 10.10.1 - 10.10.2 - 10.10.3 - 10.10.4 - 10.10.5 - 10.10.6 - 10.11.0 - 10.11.1 - 10.11.2 - 10.12.0 - 10.12.1 - 10.13.0 - 10.13.1 - 10.13.2 - 10.14.0 - 10.15.0 - 10.15.1 - 10.16.0 - 10.16.1 - 10.17.0 - 10.17.10 - 10.17.12 - 10.17.13 - 10.17.14 - 10.17.15 - 10.17.16 - 10.17.2 - 10.17.3 - 10.17.4 - 10.17.5 - 10.17.6 - 10.17.7 - 10.17.8 - 10.17.9 - 10.18.0 - 10.18.1 - 10.18.2 - 10.18.3 - 10.19.0 - 10.2.0 - 10.2.1 - 10.20.0 - 10.20.1 - 10.20.2 - 10.20.4 - 10.20.5 - 10.20.6 - 10.21.0 - 10.21.1 - 10.22.0 - 10.23.0 - 10.24.0 - 10.25.0 - 10.3.0 - 10.4.0 - 10.4.1 - 10.4.2 - 10.4.3 - 10.4.4 - 10.4.5 - 10.4.6 - 10.5.0 - 10.5.1 - 10.6.0 - 10.6.1 - 10.7.0 - 10.7.1 - 10.7.2 - 10.8.0 - 10.9.0 - 8.24.0 - 8.25.1 - 8.25.2 - 8.26.0 - 8.27.0 - 8.27.1 - 8.27.2 - 8.28.0 - 8.28.1 - 8.29.0 - 8.30.0 - 8.31.0 - 8.32.0 - 8.33.0 - 8.34.0 - 8.35.0 - 8.36.0 - 8.36.1 - 8.37.0 - 8.38.0 - 8.38.1 - 8.39.0 - 8.39.1 - 8.40.0 - 8.41.0 - 8.41.1 - 8.41.2 - 8.42.0 - 8.42.1 - 8.43.0 - 8.44.0 - 8.45.0 - 8.45.1 - 8.45.2 - 8.45.3 - 8.46.0 - 8.46.1 - 8.46.2 - 8.46.3 - 8.47.0 - 8.47.1 - 8.48.0 - 8.48.1 - 8.48.2 - 8.48.3 - 8.48.4 - 8.49.0 - 8.50.0 - 8.50.1 - 8.51.0 - 8.52.0 - 8.52.1 - 8.52.2 - 8.53.0 - 8.54.0 - 8.54.1 - 8.54.2 - 8.54.3 - 8.54.4 - 8.55.0 - 8.57.1 - 8.58.0 - 8.59.0 - 8.60.0 - 8.61.0 - 8.61.1 - 8.61.2 - 8.62.0 - 8.62.1 - 8.62.10 - 8.62.11 - 8.62.12 - 8.62.13 - 8.62.2 - 8.62.3 - 8.62.4 - 8.62.5 - 8.62.6 - 8.62.7 - 8.62.8 - 8.62.9 - 8.63.0 - 8.63.1 - 8.64.0 - 8.65.0 - 8.66.0 - 8.67.0 - 8.68.0 - 8.68.1 - 8.68.2 - 8.69.0 - 8.70.0 - 8.71.0 - 8.72.0 - 8.73.0 - 8.74.0 - 8.75.0 - 8.75.1 - 8.76.0 - 9.0.0 - 9.0.1 - 9.0.2 - 9.0.3 - 9.0.4 - 9.0.5 - 9.0.6 - 9.2.0 - 9.2.1 - 9.3.1 references: - type: WEB url: https://github.com/doobidoo/mcp-memory-service/security/advisories/GHSA-g9rg-8vq5-mpwm - type: ADVISORY url: https://nvd.nist.gov/vuln/detail/CVE-2026-33010 - type: PACKAGE url: https://github.com/doobidoo/mcp-memory-service - type: PACKAGE url: https://pypi.org/project/mcp-memory-service - type: ADVISORY url: https://github.com/advisories/GHSA-g9rg-8vq5-mpwm severity: - type: CVSS_V3 score: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N