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.
This commit is contained in:
parent
908d0ee36a
commit
15ecb59a96
2 changed files with 37 additions and 6 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -92,6 +92,29 @@ const serverTransports: Map<string, Transport> = new Map<string, Transport>(); /
|
|||
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<Transport> => {
|
|||
}
|
||||
};
|
||||
|
||||
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}`);
|
||||
|
|
|
|||
Loading…
Reference in a new issue