T09 · Insecure Skill Coding Practices
Error
- Location
- src/index.ts:1719
- Finding
- Unauthenticated Network-Accessible MCP Transport Allows Unauthorized API Quota Use## Vulnerability Details **File Location**: `src/index.ts`, lines 1719-1749 **Vulnerability Type**: Unauthenticated remote service and insufficient access control **Risk Level**: High ### Vulnerable 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: "predictfun-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 clients. Any party able to reach the listening port can create an SSE session through `/sse` and submit MCP messages through `/messages`. Calling `app.listen(port)` without specifying a host normally binds the service to all available interfaces, even though the log message claims that it is listening on `localhost`. The project documentation also presents this transport as suitable for remote deployments. Consequently, the service may become accessible from other hosts when the port is exposed by a host firewall, container configuration, cloud security group, or reverse proxy. Session IDs prevent posting to a nonexistent session but do not constitute authe ...[truncated 2241 chars]
- Remediation
- ## Remediation Suggestions 1. **Bind locally by default** - Replace `app.listen(port)` with `app.listen(port, "127.0.0.1")`. - Require an explicit, security-conscious configuration option before binding to non-loopback interfaces. - Ensure log messages report the actual bind address. 2. **Require authentication for remote access** - Protect both `/sse` and `/messages` with a strong bearer token, mutual TLS, or an authenticated reverse proxy. - Validate authentication before allocating a session. - Use constant-time token comparison where application-managed static tokens are supported. - Never treat possession of an MCP session ID as proof of client identity. 3. **Add authorization controls** - Restrict sensitive or costly tools, especially `query_subgraph`, to explicitly authorized clients. - Consider disabling arbitrary GraphQL queries in remotely exposed deployments. - Apply per-client quotas and allowlists for supported tools and subgraphs. 4. **Add abuse protections** - Enforce per-client and global rate limits. - Limit concurrent SSE sessions and requests. - Apply request-body size limits and request timeouts. - Bound GraphQL query length, depth, alias count, and complexity. - Add upstream fetch timeouts and cancellation. - Close idle sessions and cap session lifetimes. 5. **Secure deployment guidance** - Document that remote mode must not be directly exposed to untrusted networks. - Require TLS for remote traffic through a properly configured reverse proxy or native HTTPS listener. - Provide firewall, container port-publication, and cloud security-group guidance. - Remove or revise documentation that implies unauthenticated SSE mode is safe for remote deployment. 6. **Protect the upstream credential** - Configure spending and usage limits for `GRAPH_API_KEY` where supported. - Monitor abnormal query volume and rotate the key after su ...[truncated 161 chars]
