PYSEC-2026-2625

See a problem?
Import Source
https://github.com/pypa/advisory-database/blob/main/vulns/mcp-memory-service/PYSEC-2026-2625.yaml
JSON Data
https://api.osv.dev/v1/vulns/PYSEC-2026-2625
Aliases
Published
2026-07-13T14:36:41.458247Z
Modified
2026-07-13T16:32:13.301820998Z
Severity
  • 8.1 (High) CVSS_V3 - CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N CVSS Calculator
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.

References

Affected packages

PyPI / mcp-memory-service

Package

Name
mcp-memory-service
View open source insights on deps.dev
Purl
pkg:pypi/mcp-memory-service

Affected ranges

Type
ECOSYSTEM
Events
Introduced
0Unknown introduced version / All previous versions are affected
Fixed
10.25.1

Affected versions

8.*
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.2
8.62.3
8.62.4
8.62.5
8.62.6
8.62.7
8.62.8
8.62.9
8.62.10
8.62.11
8.62.12
8.62.13
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.*
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
10.*
10.0.0
10.0.1
10.0.2
10.0.3
10.1.0
10.1.1
10.1.2
10.2.0
10.2.1
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
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.2
10.17.3
10.17.4
10.17.5
10.17.6
10.17.7
10.17.8
10.17.9
10.17.10
10.17.12
10.17.13
10.17.14
10.17.15
10.17.16
10.18.0
10.18.1
10.18.2
10.18.3
10.19.0
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

Database specific

source
"https://github.com/pypa/advisory-database/blob/main/vulns/mcp-memory-service/PYSEC-2026-2625.yaml"