-
Notifications
You must be signed in to change notification settings - Fork 571
Add support for DNS rebinding protections #284
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package io.modelcontextprotocol.server.transport; | ||
|
||
import java.util.Collections; | ||
import java.util.HashSet; | ||
import java.util.Set; | ||
|
||
/** | ||
* Configuration for DNS rebinding protection in SSE server transports. Provides | ||
* validation for Host and Origin headers to prevent DNS rebinding attacks. | ||
*/ | ||
public class DnsRebindingProtectionConfig { | ||
ddworken marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
private final Set<String> allowedHosts; | ||
|
||
private final Set<String> allowedOrigins; | ||
|
||
private final boolean enableDnsRebindingProtection; | ||
ddworken marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
private DnsRebindingProtectionConfig(Builder builder) { | ||
ddworken marked this conversation as resolved.
Show resolved
Hide resolved
|
||
this.allowedHosts = Collections.unmodifiableSet(new HashSet<>(builder.allowedHosts)); | ||
this.allowedOrigins = Collections.unmodifiableSet(new HashSet<>(builder.allowedOrigins)); | ||
this.enableDnsRebindingProtection = builder.enableDnsRebindingProtection; | ||
} | ||
|
||
/** | ||
* Validates Host and Origin headers for DNS rebinding protection. Returns true if the | ||
* headers are valid, false otherwise. | ||
* @param hostHeader The value of the Host header (may be null) | ||
* @param originHeader The value of the Origin header (may be null) | ||
* @return true if the headers are valid, false otherwise | ||
*/ | ||
public boolean validate(String hostHeader, String originHeader) { | ||
ddworken marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Skip validation if protection is not enabled | ||
if (!enableDnsRebindingProtection) { | ||
return true; | ||
} | ||
|
||
// Validate Host header | ||
if (hostHeader != null) { | ||
String lowerHost = hostHeader.toLowerCase(); | ||
if (!allowedHosts.contains(lowerHost)) { | ||
return false; | ||
} | ||
} | ||
|
||
// Validate Origin header | ||
if (originHeader != null) { | ||
String lowerOrigin = originHeader.toLowerCase(); | ||
if (!allowedOrigins.contains(lowerOrigin)) { | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
public static Builder builder() { | ||
ddworken marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return new Builder(); | ||
} | ||
|
||
public static class Builder { | ||
ddworken marked this conversation as resolved.
Show resolved
Hide resolved
ddworken marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
private final Set<String> allowedHosts = new HashSet<>(); | ||
|
||
private final Set<String> allowedOrigins = new HashSet<>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even though the spec states:
It doesn't make sense to me because I don't think validating the DNS rebinding is all about changing the underlying IP address associated to a domain name ( In summary, the MCP server that receives the request should validate that it’s own host matches the host being requested. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I agree that this is the most critical part of DNS rebinding protection, though it is pretty common to also check the Origin header since it provides another layer of defense and also mitigates a variety of other web-based attacks.
Modern browsers send the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Can you please provide references to other MCP implementations that validate the
What other web-based attacks are you referring to? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We also are hoping to enable this for other MCP implementations too.
E.g. CSRF attacks. If an MCP server accepts simple requests then it can be CSRF attacked, and validating the Origin header blocks this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The 2x references you provided are PR's submitted by you and only merged last month. Do you have other references where the
This is not correct. The recommended mitigation for CSRF attack are the use of tokens. See token-based mitigation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I definitely agree that checking the
Nowadays, checking the |
||
|
||
private boolean enableDnsRebindingProtection = true; | ||
ddworken marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
public Builder allowedHost(String host) { | ||
if (host != null) { | ||
this.allowedHosts.add(host.toLowerCase()); | ||
} | ||
return this; | ||
} | ||
|
||
public Builder allowedHosts(Set<String> hosts) { | ||
if (hosts != null) { | ||
hosts.forEach(this::allowedHost); | ||
} | ||
return this; | ||
} | ||
|
||
public Builder allowedOrigin(String origin) { | ||
if (origin != null) { | ||
this.allowedOrigins.add(origin.toLowerCase()); | ||
} | ||
return this; | ||
} | ||
|
||
public Builder allowedOrigins(Set<String> origins) { | ||
if (origins != null) { | ||
origins.forEach(this::allowedOrigin); | ||
} | ||
return this; | ||
} | ||
|
||
public Builder enableDnsRebindingProtection(boolean enable) { | ||
ddworken marked this conversation as resolved.
Show resolved
Hide resolved
|
||
this.enableDnsRebindingProtection = enable; | ||
return this; | ||
} | ||
|
||
public DnsRebindingProtectionConfig build() { | ||
return new DnsRebindingProtectionConfig(this); | ||
} | ||
|
||
} | ||
|
||
} |
Uh oh!
There was an error while loading. Please reload this page.