diff --git a/README.md b/README.md index 42829645..122e86de 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,14 @@ HOST=0.0.0.0 npm start **Warning:** Only bind to all interfaces in trusted network environments, as this exposes the proxy server's ability to execute local processes. +#### DNS Rebinding Protection + +To prevent DNS rebinding attacks, the MCP Inspector validates the `Origin` header on incoming requests. By default, only requests from the client origin are allowed (respects `CLIENT_PORT` if set, defaulting to port 6274). You can configure additional allowed origins by setting the `ALLOWED_ORIGINS` environment variable (comma-separated list): + +```bash +ALLOWED_ORIGINS=http://localhost:6274,http://127.0.0.1:6274,http://localhost:8000 npm start +``` + ### Configuration The MCP Inspector supports the following configuration settings. To change them, click on the `Configuration` button in the MCP Inspector UI: diff --git a/server/src/index.ts b/server/src/index.ts index 5ba4788f..5b3b0c50 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -92,6 +92,29 @@ const serverTransports: Map = new Map(); / const sessionToken = randomBytes(32).toString('hex'); const authDisabled = !!process.env.DANGEROUSLY_OMIT_AUTH; +// Origin validation middleware to prevent DNS rebinding attacks +const originValidationMiddleware = (req: express.Request, res: express.Response, next: express.NextFunction) => { + const origin = req.headers.origin; + + // Default origins based on CLIENT_PORT or use environment variable + const clientPort = process.env.CLIENT_PORT || '6274'; + const defaultOrigins = [ + `http://localhost:${clientPort}`, + `http://127.0.0.1:${clientPort}` + ]; + const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || defaultOrigins; + + if (origin && !allowedOrigins.includes(origin)) { + console.error(`Invalid origin: ${origin}`); + res.status(403).json({ + error: 'Forbidden - invalid origin', + message: 'Request blocked to prevent DNS rebinding attacks. Configure allowed origins via environment variable.' + }); + return; + } + next(); +}; + const authMiddleware = (req: express.Request, res: express.Response, next: express.NextFunction) => { if (authDisabled) { return next(); @@ -169,7 +192,7 @@ const createTransport = async (req: express.Request): Promise => { } }; -app.get("/mcp", authMiddleware, async (req, res) => { +app.get("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => { const sessionId = req.headers["mcp-session-id"] as string; console.log(`Received GET message for sessionId ${sessionId}`); try { @@ -188,7 +211,7 @@ app.get("/mcp", authMiddleware, async (req, res) => { } }); -app.post("/mcp", authMiddleware, async (req, res) => { +app.post("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => { const sessionId = req.headers["mcp-session-id"] as string | undefined; let serverTransport: Transport | undefined; if (!sessionId) { @@ -258,7 +281,7 @@ app.post("/mcp", authMiddleware, async (req, res) => { } }); -app.delete("/mcp", authMiddleware, async (req, res) => { +app.delete("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => { const sessionId = req.headers["mcp-session-id"] as string | undefined; console.log(`Received DELETE message for sessionId ${sessionId}`); let serverTransport: Transport | undefined; @@ -285,7 +308,7 @@ app.delete("/mcp", authMiddleware, async (req, res) => { } }); -app.get("/stdio", authMiddleware, async (req, res) => { +app.get("/stdio", originValidationMiddleware, authMiddleware, async (req, res) => { try { console.log("New STDIO connection request"); let serverTransport: Transport | undefined; @@ -347,7 +370,7 @@ app.get("/stdio", authMiddleware, async (req, res) => { } }); -app.get("/sse", authMiddleware, async (req, res) => { +app.get("/sse", originValidationMiddleware, authMiddleware, async (req, res) => { try { console.log( "New SSE connection request. NOTE: The sse transport is deprecated and has been replaced by StreamableHttp", @@ -396,7 +419,7 @@ app.get("/sse", authMiddleware, async (req, res) => { } }); -app.post("/message", authMiddleware, async (req, res) => { +app.post("/message", originValidationMiddleware, authMiddleware, async (req, res) => { try { const sessionId = req.query.sessionId; console.log(`Received POST message for sessionId ${sessionId}`);