Skip to content

Commit 15ecb59

Browse files
feat: add origin validation to prevent DNS rebinding attacks
- Add origin validation middleware to check Origin header - Default allowed origins respect CLIENT_PORT environment variable - Support ALLOWED_ORIGINS environment variable for additional origins - Apply validation to all protected endpoints before auth check - Return 403 Forbidden with clear message for invalid origins - Add DNS Rebinding Protection section to README This completes the security hardening by preventing malicious websites from making requests to the local proxy server.
1 parent 908d0ee commit 15ecb59

File tree

2 files changed

+37
-6
lines changed

2 files changed

+37
-6
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,14 @@ HOST=0.0.0.0 npm start
176176

177177
**Warning:** Only bind to all interfaces in trusted network environments, as this exposes the proxy server's ability to execute local processes.
178178

179+
#### DNS Rebinding Protection
180+
181+
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):
182+
183+
```bash
184+
ALLOWED_ORIGINS=http://localhost:6274,http://127.0.0.1:6274,http://localhost:8000 npm start
185+
```
186+
179187
### Configuration
180188

181189
The MCP Inspector supports the following configuration settings. To change them, click on the `Configuration` button in the MCP Inspector UI:

server/src/index.ts

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,29 @@ const serverTransports: Map<string, Transport> = new Map<string, Transport>(); /
9292
const sessionToken = randomBytes(32).toString('hex');
9393
const authDisabled = !!process.env.DANGEROUSLY_OMIT_AUTH;
9494

95+
// Origin validation middleware to prevent DNS rebinding attacks
96+
const originValidationMiddleware = (req: express.Request, res: express.Response, next: express.NextFunction) => {
97+
const origin = req.headers.origin;
98+
99+
// Default origins based on CLIENT_PORT or use environment variable
100+
const clientPort = process.env.CLIENT_PORT || '6274';
101+
const defaultOrigins = [
102+
`http://localhost:${clientPort}`,
103+
`http://127.0.0.1:${clientPort}`
104+
];
105+
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || defaultOrigins;
106+
107+
if (origin && !allowedOrigins.includes(origin)) {
108+
console.error(`Invalid origin: ${origin}`);
109+
res.status(403).json({
110+
error: 'Forbidden - invalid origin',
111+
message: 'Request blocked to prevent DNS rebinding attacks. Configure allowed origins via environment variable.'
112+
});
113+
return;
114+
}
115+
next();
116+
};
117+
95118
const authMiddleware = (req: express.Request, res: express.Response, next: express.NextFunction) => {
96119
if (authDisabled) {
97120
return next();
@@ -169,7 +192,7 @@ const createTransport = async (req: express.Request): Promise<Transport> => {
169192
}
170193
};
171194

172-
app.get("/mcp", authMiddleware, async (req, res) => {
195+
app.get("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => {
173196
const sessionId = req.headers["mcp-session-id"] as string;
174197
console.log(`Received GET message for sessionId ${sessionId}`);
175198
try {
@@ -188,7 +211,7 @@ app.get("/mcp", authMiddleware, async (req, res) => {
188211
}
189212
});
190213

191-
app.post("/mcp", authMiddleware, async (req, res) => {
214+
app.post("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => {
192215
const sessionId = req.headers["mcp-session-id"] as string | undefined;
193216
let serverTransport: Transport | undefined;
194217
if (!sessionId) {
@@ -258,7 +281,7 @@ app.post("/mcp", authMiddleware, async (req, res) => {
258281
}
259282
});
260283

261-
app.delete("/mcp", authMiddleware, async (req, res) => {
284+
app.delete("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => {
262285
const sessionId = req.headers["mcp-session-id"] as string | undefined;
263286
console.log(`Received DELETE message for sessionId ${sessionId}`);
264287
let serverTransport: Transport | undefined;
@@ -285,7 +308,7 @@ app.delete("/mcp", authMiddleware, async (req, res) => {
285308
}
286309
});
287310

288-
app.get("/stdio", authMiddleware, async (req, res) => {
311+
app.get("/stdio", originValidationMiddleware, authMiddleware, async (req, res) => {
289312
try {
290313
console.log("New STDIO connection request");
291314
let serverTransport: Transport | undefined;
@@ -347,7 +370,7 @@ app.get("/stdio", authMiddleware, async (req, res) => {
347370
}
348371
});
349372

350-
app.get("/sse", authMiddleware, async (req, res) => {
373+
app.get("/sse", originValidationMiddleware, authMiddleware, async (req, res) => {
351374
try {
352375
console.log(
353376
"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) => {
396419
}
397420
});
398421

399-
app.post("/message", authMiddleware, async (req, res) => {
422+
app.post("/message", originValidationMiddleware, authMiddleware, async (req, res) => {
400423
try {
401424
const sessionId = req.query.sessionId;
402425
console.log(`Received POST message for sessionId ${sessionId}`);

0 commit comments

Comments
 (0)