T09 · Insecure Skill Coding Practices
- Location
src/index.ts:1791- Finding
Unauthenticated MCP HTTP/SSE Service Binds to All Network Interfaces
- Content
View full analysis
Vulnerability Details
File Location:
src/index.ts:1791-1819
Vulnerability Type: Unauthenticated externally reachable MCP service
Risk Level: High when HTTP/SSE mode is enabledVulnerable Code
ts function startHttpTransport(port: number) { const app = express(); const sessions = new Map<string, SSEServerTransport>(); app.get("/sse", async (req, res) => { const transport = new SSEServerTransport("/messages", res); sessions.set(transport.sessionId, transport); res.on("close", () => { sessions.delete(transport.sessionId); }); await server.connect(transport); }); app.post("/messages", async (req, res) => { const sessionId = req.query.sessionId as string; const transport = sessions.get(sessionId); if (!transport) { res.status(400).json({ error: "Invalid or expired session" }); return; } await transport.handlePostMessage(req, res); }); app.get("/health", (_req, res) => { res.json({ status: "ok", server: "graph-polymarket-mcp" }); }); app.listen(port, () => { console.error(`SSE transport listening on http://localhost:${port}/sse`); }); }Technical Analysis
The HTTP/SSE transport does not authenticate or authorize requests to
/sseor/messages. Any client that can reach the listener can create an MCP session and invoke the registered tools.Additionally,
app.listen(port)does not explicitly restrict the listener to loopback. In a typical Node.js deployment, this listens on an unspecified address covering all available network interfaces. The log message claims that the endpoint is available atlocalhost, but this does not enforce localhost-only access.This becomes exploitable when HTTP mode is enabled through
--http,--http-only, orMCP_HTTP_PORT. The exposed tools include custom GraphQL querying through the operator'sGRAPH_API_KEY. ...[truncated 1821 chars]- Remediation
View remediation
Remediation Suggestions
-
Bind to loopback explicitly by default:
ts app.listen(port, "127.0.0.1", () => { console.error(`SSE transport listening on http://127.0.0.1:${port}/sse`); }); -
Require a separate, explicit configuration option before allowing a non-loopback bind address. Display a prominent warning when external binding is selected.
-
Protect both
/sseand/messageswith authentication, such as a high-entropy bearer token or authentication enforced by a trusted reverse proxy. -
Apply authorization consistently to session creation and message submission. Do not treat possession of a session identifier as sufficient authorization.
-
Validate
OriginandHostheaders against an operator-configured allowlist to reduce browser-based and DNS-rebinding attack exposure. -
Add per-client rate limits, maximum concurrent-session limits, idle session expiration, request-size limits, and upstream query budgets.
-
When remote access is required, terminate TLS at a trusted reverse proxy and restrict access using firewall rules or a private network.
-
Update the startup log to report the actual bind address rather than always claiming that the service is on localhost.
-
Add automated tests verifying that default HTTP mode is reachable only through loopback and that unauthenticated requests are rejected when authentication is configured.
-
