diff --git a/README.md b/README.md index ccc627b2..32037f90 100644 --- a/README.md +++ b/README.md @@ -254,7 +254,7 @@ app.post('/mcp', async (req, res) => { }, // DNS rebinding protection is disabled by default for backwards compatibility. If you are running this server // locally, make sure to set: - // disableDnsRebindingProtection: true, + // enableDnsRebindingProtection: true, // allowedHosts: ['127.0.0.1'], }); @@ -399,7 +399,7 @@ The Streamable HTTP transport includes DNS rebinding protection to prevent secur ```typescript const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), - disableDnsRebindingProtection: false, + enableDnsRebindingProtection: true, allowedHosts: ['127.0.0.1', ...], allowedOrigins: ['https://yourdomain.com', 'https://www.yourdomain.com'] diff --git a/src/server/sse.test.ts b/src/server/sse.test.ts index 9fb1f30c..aee6eaf6 100644 --- a/src/server/sse.test.ts +++ b/src/server/sse.test.ts @@ -125,6 +125,7 @@ describe('SSEServerTransport', () => { const mockRes = createMockResponse(); const transport = new SSEServerTransport('/messages', mockRes, { allowedHosts: ['localhost:3000', 'example.com'], + enableDnsRebindingProtection: true, }); await transport.start(); @@ -144,6 +145,7 @@ describe('SSEServerTransport', () => { const mockRes = createMockResponse(); const transport = new SSEServerTransport('/messages', mockRes, { allowedHosts: ['localhost:3000'], + enableDnsRebindingProtection: true, }); await transport.start(); @@ -163,6 +165,7 @@ describe('SSEServerTransport', () => { const mockRes = createMockResponse(); const transport = new SSEServerTransport('/messages', mockRes, { allowedHosts: ['localhost:3000'], + enableDnsRebindingProtection: true, }); await transport.start(); @@ -183,6 +186,7 @@ describe('SSEServerTransport', () => { const mockRes = createMockResponse(); const transport = new SSEServerTransport('/messages', mockRes, { allowedOrigins: ['http://localhost:3000', 'https://example.com'], + enableDnsRebindingProtection: true, }); await transport.start(); @@ -202,6 +206,7 @@ describe('SSEServerTransport', () => { const mockRes = createMockResponse(); const transport = new SSEServerTransport('/messages', mockRes, { allowedOrigins: ['http://localhost:3000'], + enableDnsRebindingProtection: true, }); await transport.start(); @@ -268,13 +273,13 @@ describe('SSEServerTransport', () => { }); }); - describe('disableDnsRebindingProtection option', () => { - it('should skip all validations when disableDnsRebindingProtection is true', async () => { + describe('enableDnsRebindingProtection option', () => { + it('should skip all validations when enableDnsRebindingProtection is false', async () => { const mockRes = createMockResponse(); const transport = new SSEServerTransport('/messages', mockRes, { allowedHosts: ['localhost:3000'], allowedOrigins: ['http://localhost:3000'], - disableDnsRebindingProtection: true, + enableDnsRebindingProtection: false, }); await transport.start(); @@ -300,6 +305,7 @@ describe('SSEServerTransport', () => { const transport = new SSEServerTransport('/messages', mockRes, { allowedHosts: ['localhost:3000'], allowedOrigins: ['http://localhost:3000'], + enableDnsRebindingProtection: true, }); await transport.start(); diff --git a/src/server/sse.ts b/src/server/sse.ts index 73c74bb3..bd5d80b9 100644 --- a/src/server/sse.ts +++ b/src/server/sse.ts @@ -26,9 +26,10 @@ export interface SSEServerTransportOptions { allowedOrigins?: string[]; /** - * Disable DNS rebinding protection entirely (overrides allowedHosts and allowedOrigins). + * Enable DNS rebinding protection (requires allowedHosts and/or allowedOrigins to be configured). + * Default is false for backwards compatibility. */ - disableDnsRebindingProtection?: boolean; + enableDnsRebindingProtection?: boolean; } /** @@ -54,7 +55,7 @@ export class SSEServerTransport implements Transport { options?: SSEServerTransportOptions, ) { this._sessionId = randomUUID(); - this._options = options || {disableDnsRebindingProtection: true}; + this._options = options || {enableDnsRebindingProtection: false}; } /** @@ -62,8 +63,8 @@ export class SSEServerTransport implements Transport { * @returns Error message if validation fails, undefined if validation passes. */ private validateRequestHeaders(req: IncomingMessage): string | undefined { - // Skip validation if protection is disabled - if (this._options.disableDnsRebindingProtection) { + // Skip validation if protection is not enabled + if (!this._options.enableDnsRebindingProtection) { return undefined; } diff --git a/src/server/streamableHttp.test.ts b/src/server/streamableHttp.test.ts index 68fe8ee7..4683024b 100644 --- a/src/server/streamableHttp.test.ts +++ b/src/server/streamableHttp.test.ts @@ -1312,7 +1312,7 @@ describe("StreamableHTTPServerTransport DNS rebinding protection", () => { const result = await createTestServerWithDnsProtection({ sessionIdGenerator: undefined, allowedHosts: ['localhost:3001'], - disableDnsRebindingProtection: false, + enableDnsRebindingProtection: true, }); server = result.server; transport = result.transport; @@ -1338,7 +1338,7 @@ describe("StreamableHTTPServerTransport DNS rebinding protection", () => { const result = await createTestServerWithDnsProtection({ sessionIdGenerator: undefined, allowedHosts: ['example.com:3001'], - disableDnsRebindingProtection: false, + enableDnsRebindingProtection: true, }); server = result.server; transport = result.transport; @@ -1362,7 +1362,7 @@ describe("StreamableHTTPServerTransport DNS rebinding protection", () => { const result = await createTestServerWithDnsProtection({ sessionIdGenerator: undefined, allowedHosts: ['example.com:3001'], - disableDnsRebindingProtection: false, + enableDnsRebindingProtection: true, }); server = result.server; transport = result.transport; @@ -1384,7 +1384,7 @@ describe("StreamableHTTPServerTransport DNS rebinding protection", () => { const result = await createTestServerWithDnsProtection({ sessionIdGenerator: undefined, allowedOrigins: ['http://localhost:3000', 'https://example.com'], - disableDnsRebindingProtection: false, + enableDnsRebindingProtection: true, }); server = result.server; transport = result.transport; @@ -1407,7 +1407,7 @@ describe("StreamableHTTPServerTransport DNS rebinding protection", () => { const result = await createTestServerWithDnsProtection({ sessionIdGenerator: undefined, allowedOrigins: ['http://localhost:3000'], - disableDnsRebindingProtection: false, + enableDnsRebindingProtection: true, }); server = result.server; transport = result.transport; @@ -1429,13 +1429,13 @@ describe("StreamableHTTPServerTransport DNS rebinding protection", () => { }); }); - describe("disableDnsRebindingProtection option", () => { - it("should skip all validations when disableDnsRebindingProtection is true", async () => { + describe("enableDnsRebindingProtection option", () => { + it("should skip all validations when enableDnsRebindingProtection is false", async () => { const result = await createTestServerWithDnsProtection({ sessionIdGenerator: undefined, allowedHosts: ['localhost:3001'], allowedOrigins: ['http://localhost:3000'], - disableDnsRebindingProtection: true, + enableDnsRebindingProtection: false, }); server = result.server; transport = result.transport; @@ -1463,7 +1463,7 @@ describe("StreamableHTTPServerTransport DNS rebinding protection", () => { sessionIdGenerator: undefined, allowedHosts: ['localhost:3001'], allowedOrigins: ['http://localhost:3001'], - disableDnsRebindingProtection: false, + enableDnsRebindingProtection: true, }); server = result.server; transport = result.transport; @@ -1507,7 +1507,7 @@ async function createTestServerWithDnsProtection(config: { sessionIdGenerator: (() => string) | undefined; allowedHosts?: string[]; allowedOrigins?: string[]; - disableDnsRebindingProtection?: boolean; + enableDnsRebindingProtection?: boolean; }): Promise<{ server: Server; transport: StreamableHTTPServerTransport; @@ -1523,7 +1523,7 @@ async function createTestServerWithDnsProtection(config: { sessionIdGenerator: config.sessionIdGenerator, allowedHosts: config.allowedHosts, allowedOrigins: config.allowedOrigins, - disableDnsRebindingProtection: config.disableDnsRebindingProtection, + enableDnsRebindingProtection: config.enableDnsRebindingProtection, }); await mcpServer.connect(transport); diff --git a/src/server/streamableHttp.ts b/src/server/streamableHttp.ts index 8feeac55..084147dc 100644 --- a/src/server/streamableHttp.ts +++ b/src/server/streamableHttp.ts @@ -75,10 +75,10 @@ export interface StreamableHTTPServerTransportOptions { allowedOrigins?: string[]; /** - * Disable DNS rebinding protection entirely (overrides allowedHosts and allowedOrigins). - * Default is true for backwards compatibility. + * Enable DNS rebinding protection (requires allowedHosts and/or allowedOrigins to be configured). + * Default is false for backwards compatibility. */ - disableDnsRebindingProtection?: boolean; + enableDnsRebindingProtection?: boolean; } /** @@ -129,7 +129,7 @@ export class StreamableHTTPServerTransport implements Transport { private _onsessioninitialized?: (sessionId: string) => void; private _allowedHosts?: string[]; private _allowedOrigins?: string[]; - private _disableDnsRebindingProtection: boolean; + private _enableDnsRebindingProtection: boolean; sessionId?: string | undefined; onclose?: () => void; @@ -143,7 +143,7 @@ export class StreamableHTTPServerTransport implements Transport { this._onsessioninitialized = options.onsessioninitialized; this._allowedHosts = options.allowedHosts; this._allowedOrigins = options.allowedOrigins; - this._disableDnsRebindingProtection = options.disableDnsRebindingProtection ?? true; + this._enableDnsRebindingProtection = options.enableDnsRebindingProtection ?? false; } /** @@ -162,8 +162,8 @@ export class StreamableHTTPServerTransport implements Transport { * @returns Error message if validation fails, undefined if validation passes. */ private validateRequestHeaders(req: IncomingMessage): string | undefined { - // Skip validation if protection is disabled - if (this._disableDnsRebindingProtection) { + // Skip validation if protection is not enabled + if (!this._enableDnsRebindingProtection) { return undefined; }