DNS rebinding protection: check host header

Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
This commit is contained in:
Daniel Garnier-Moiroux 2026-02-05 16:12:04 +01:00 • committed by Daniel Garnier-Moiroux
commit f6c3fde7f4

View file

@ -48,6 +48,8 @@ class ServerTransportSecurityIntegrationTests {
private static final String DISALLOWED_ORIGIN = "https://malicious.example.com";
private static final String DISALLOWED_HOST = "malicious.example.com:8080";
@Parameter
private static Transport transport;
@ -73,6 +75,7 @@ class ServerTransportSecurityIntegrationTests {
@BeforeEach
void setUp() {
requestCustomizer.reset();
mcpClient = transport.createMcpClient(baseUrl, requestCustomizer);
}
@ -115,6 +118,29 @@ class ServerTransportSecurityIntegrationTests {
assertThatThrownBy(() -> mcpClient.listTools());
}
@Test
void hostAllowed() {
// Host header is set by default by HttpClient to the request URI host
var result = mcpClient.initialize();
var tools = mcpClient.listTools();
assertThat(result.protocolVersion()).isNotEmpty();
assertThat(tools.tools()).isEmpty();
}
@Test
void connectHostNotAllowed() {
requestCustomizer.setHostHeader(DISALLOWED_HOST);
assertThatThrownBy(() -> mcpClient.initialize());
}
@Test
void messageHostNotAllowed() {
mcpClient.initialize();
requestCustomizer.setHostHeader(DISALLOWED_HOST);
assertThatThrownBy(() -> mcpClient.listTools());
}
// ----------------------------------------------------
// Tomcat management
// ----------------------------------------------------
@ -182,8 +208,10 @@ class ServerTransportSecurityIntegrationTests {
public Sse() {
transport = HttpServletSseServerTransportProvider.builder()
.messageEndpoint("/mcp/message")
.securityValidator(
DefaultServerTransportSecurityValidator.builder().allowedOrigin("http://localhost:*").build())
.securityValidator(DefaultServerTransportSecurityValidator.builder()
.allowedOrigin("http://localhost:*")
.allowedHost("localhost:*")
.build())
.build();
McpServer.sync(transport)
.serverInfo("test-server", "1.0.0")
@ -213,8 +241,10 @@ class ServerTransportSecurityIntegrationTests {
public StreamableHttp() {
transport = HttpServletStreamableServerTransportProvider.builder()
.securityValidator(
DefaultServerTransportSecurityValidator.builder().allowedOrigin("http://localhost:*").build())
.securityValidator(DefaultServerTransportSecurityValidator.builder()
.allowedOrigin("http://localhost:*")
.allowedHost("localhost:*")
.build())
.build();
McpServer.sync(transport)
.serverInfo("test-server", "1.0.0")
@ -245,8 +275,10 @@ class ServerTransportSecurityIntegrationTests {
public Stateless() {
transport = HttpServletStatelessServerTransport.builder()
.securityValidator(
DefaultServerTransportSecurityValidator.builder().allowedOrigin("http://localhost:*").build())
.securityValidator(DefaultServerTransportSecurityValidator.builder()
.allowedOrigin("http://localhost:*")
.allowedHost("localhost:*")
.build())
.build();
McpServer.sync(transport)
.serverInfo("test-server", "1.0.0")
@ -275,18 +307,33 @@ class ServerTransportSecurityIntegrationTests {
private String originHeader = null;
private String hostHeader = null;
@Override
public void customize(HttpRequest.Builder builder, String method, URI endpoint, String body,
McpTransportContext context) {
if (originHeader != null) {
builder.header("Origin", originHeader);
}
if (hostHeader != null) {
// HttpClient normally sets Host automatically, but we can override it
builder.header("Host", hostHeader);
}
}
public void setOriginHeader(String originHeader) {
this.originHeader = originHeader;
}
public void setHostHeader(String hostHeader) {
this.hostHeader = hostHeader;
}
public void reset() {
this.originHeader = null;
this.hostHeader = null;
}
}
}