GHSA-G9RG-8VQ5-MPWM

Vulnerability from github – Published: 2026-03-07 02:12 – Updated: 2026-03-20 21:32
VLAI?
Summary
mcp-memory-service's Wildcard CORS with Credentials Enables Cross-Origin Memory Theft
Details

Summary

When 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.

Details

Vulnerable Code

config.py:546 - Wildcard CORS origin default

CORS_ORIGINS = os.getenv('MCP_CORS_ORIGINS', '*').split(',')

This produces ['*'] by default, allowing any origin.

app.py:274-280 - CORSMiddleware configuration

# CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=CORS_ORIGINS,        # ['*'] by default
    allow_credentials=True,             # Unnecessary for anonymous access; bad practice
    allow_methods=["*"],
    allow_headers=["*"],
)

How the Attack Works

The 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:

// Running on https://evil.com - reads victim's memories
// No credentials needed - anonymous access means the API is open
const response = await fetch('http://192.168.1.100:8000/api/memories');
const memories = await response.json();
// memories contains every stored memory - passwords, API keys, personal notes

The 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.

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.

Two Attack Vectors

This misconfiguration enables two distinct attack paths:

1. Cross-origin browser attack (CORS - this advisory) - Attacker lures victim to a malicious webpage - JavaScript on the page reads/writes the memory service API - Works from anywhere on the internet if the victim visits the page - The ACAO: * header is what allows the browser to expose the response to the attacker's JavaScript

2. Direct network access (compounding factor) - Attacker on the same network directly calls the API (curl http://<target>:8000/api/memories) - No CORS involved - CORS is a browser-only restriction - Enabled by 0.0.0.0 binding + anonymous access, independent of CORS configuration

The CORS misconfiguration specifically enables attack vector #1, extending the reach from local network to anyone who can get the victim to click a link.

Compounding Factors

  • HTTP_HOST = '0.0.0.0' - Binds to all interfaces, exposing the service to the entire network (enables attack vector #2)
  • HTTPS_ENABLED = 'false' - No TLS by default, allowing passive interception
  • 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)
  • 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
  • API key via query parameter - api_key query param is cached in browser history and server logs

Attack Scenario

  1. Victim runs mcp-memory-service with HTTP enabled and anonymous access
  2. Victim visits https://evil.com which includes JavaScript
  3. JavaScript sends fetch('http://<victim-ip>:8000/api/memories') (no credentials needed)
  4. Server responds with Access-Control-Allow-Origin: *
  5. Browser allows JavaScript to read the response - attacker receives all memories
  6. Attacker's script also calls DELETE/PUT endpoints to modify or destroy memories
  7. Victim sees a normal web page; no indication of the attack

Root Cause

The 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.

PoC

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from starlette.testclient import TestClient

app = FastAPI()
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.get("/api/memories")
def memories():
    return [{"content": "secret memory data"}]

client = TestClient(app)

# Non-credentialed request (how the real attack works with anonymous access)
response = client.get("/api/memories", headers={"Origin": "https://evil.com"})
print(response.headers["access-control-allow-origin"])  # *
print(response.json())  # [{"content": "secret memory data"}]
# Any website can read this response because ACAO is *

Impact

  • Complete cross-origin memory access: Any website can read all stored memories when the victim has the HTTP server running with anonymous access
  • Memory tampering: Write/delete endpoints are also accessible cross-origin, allowing memory destruction
  • 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
  • Silent exfiltration: The attack is invisible to the victim; no browser warnings, no popups, no indicators

Remediation

Replace the wildcard default with an explicit localhost origin:

# In config.py  (safe default)
CORS_ORIGINS = os.getenv('MCP_CORS_ORIGINS', 'http://localhost:8000,http://127.0.0.1:8000').split(',')

# In app.py - warn on wildcard
if '*' in CORS_ORIGINS:
    logger.warning("Wildcard CORS origin detected. This allows any website to access the API. "
                    "Set MCP_CORS_ORIGINS to restrict access.")

# Also: set allow_credentials=False unless specific origins are configured
app.add_middleware(
    CORSMiddleware,
    allow_origins=CORS_ORIGINS,
    allow_credentials='*' not in CORS_ORIGINS,  # Only with explicit origins
    allow_methods=["*"],
    allow_headers=["*"],
)

Affected Deployments

The 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.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "mcp-memory-service"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "10.25.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-33010"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-942"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-07T02:12:26Z",
    "nvd_published_at": "2026-03-20T19:16:17Z",
    "severity": "HIGH"
  },
  "details": "### Summary\nWhen the HTTP server is enabled (`MCP_HTTP_ENABLED=true`), the application configures FastAPI\u0027s CORSMiddleware with `allow_origins=[\u0027*\u0027]`, `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(\u0027MCP_CORS_ORIGINS\u0027, \u0027*\u0027).split(\u0027,\u0027)\n```\n\nThis produces `[\u0027*\u0027]` 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,        # [\u0027*\u0027] 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\u0027s memories\n// No credentials needed - anonymous access means the API is open\nconst response = await fetch(\u0027http://192.168.1.100:8000/api/memories\u0027);\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: \u0027include\u0027` is used. However, this is irrelevant to the attack because **anonymous access means no credentials are needed** - a plain `fetch()` without `credentials: \u0027include\u0027` 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\u0027s JavaScript\n\n**2. Direct network access (compounding factor)**\n- Attacker on the same network directly calls the API (`curl http://\u003ctarget\u003e: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 = \u00270.0.0.0\u0027`** - Binds to all interfaces, exposing the service to the entire network (enables attack vector #2)\n- **`HTTPS_ENABLED = \u0027false\u0027`** - 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(\u0027http://\u003cvictim-ip\u003e:8000/api/memories\u0027)` (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\u0027s 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\u0027t 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(\u0027MCP_CORS_ORIGINS\u0027, \u0027http://localhost:8000,http://127.0.0.1:8000\u0027).split(\u0027,\u0027)\n\n# In app.py - warn on wildcard\nif \u0027*\u0027 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=\u0027*\u0027 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.",
  "id": "GHSA-g9rg-8vq5-mpwm",
  "modified": "2026-03-20T21:32:22Z",
  "published": "2026-03-07T02:12:26Z",
  "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"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "mcp-memory-service\u0027s Wildcard CORS with Credentials Enables Cross-Origin Memory Theft"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…