Skip to content

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

ddworken
Copy link

Motivation and Context

This implements the mitigations described here. To avoid breaking existing applications this doesn't enable any changes by-default, but enabling this feature is heavily encouraged for any local MCP servers using the SSE transport.

How Has This Been Tested?

Tested via unit tests.

Breaking Changes

To avoid introducing any breaking changes, the DNS rebinding protections are disabled by default. Ideally we should find a way to enable them by default. But for now, adding them as disabled is a good first step.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

@ddworken ddworken marked this pull request as ready for review May 30, 2025 17:01
@chemicL chemicL self-assigned this Jun 11, 2025
Copy link
Member

@chemicL chemicL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the PR. I did the first round by going through the code in the mcp module. I believe the comments would apply also to the mcp-spring modules. Some questions, some suggestions here and there, but the most important themes are:

  1. HTTP(2) protocol compliance - will how to make it work when there is :authority pseudo-header instead of Host?
  2. MCP Spec compliance - does Host require checking? The spec only mentions Origin.
  3. Since validation is enforced by the spec as a MUST i do feel we should enable validation with a breaking change. @tzolov looking forward to your input here as well.
  4. Builder pattern consistency.
  5. Javadocs improvements and filling the gaps.
  6. Removing code duplication.

Thanks in advance for a follow-up and the work so far!


private final Set<String> allowedOrigins;

private final boolean enableDnsRebindingProtection;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private final boolean enableDnsRebindingProtection;
private final boolean enabled;


private final boolean enableDnsRebindingProtection;

private DnsRebindingProtectionConfig(Builder builder) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider passing explicit arguments instead of the builder. Should the builder change, and it is mutable, this constructor could create instances that are in inconsistent state. The pattern present throughout the codebase is rather to pass explicit parameters instead of this cyclic dependency.

return new Builder();
}

public static class Builder {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to avoid having two means to instantiate the Builder, the constructor should be private. A static factory exists and it is the pattern that's used by this project.

return true;
}

public static Builder builder() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing javadoc

return new Builder();
}

public static class Builder {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add javadocs to the public class and its methods.

@@ -475,6 +527,8 @@ public static class Builder {

private String sseEndpoint = DEFAULT_SSE_ENDPOINT;

private DnsRebindingProtectionConfig dnsRebindingProtectionConfig;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the default behaviour be injected always unless the user specifically overrides it?

@@ -202,6 +222,18 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response)
return;
}

// Validate headers if DNS rebinding protection is configured
if (dnsRebindingProtectionConfig != null) {
String hostHeader = request.getHeader("Host");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check AbstractMcpAsyncServerTests and the implementations. Do you think we could be able to have a generic test suite with just the transport-specific settings in the inheriting classes in the same fashion?

* Configuration for DNS rebinding protection in SSE server transports. Provides
* validation for Host and Origin headers to prevent DNS rebinding attacks.
*/
public class DnsRebindingProtectionConfig {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DnsRebindingProtectionConfig -> DnsRebindingProtection.
I view this more as an entity that has behaviour via the validate() method, not just a set of configurations, so I'd skip the "Config" part.

* @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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public boolean validate(String hostHeader, String originHeader) {
public boolean isValid(String hostHeader, String originHeader) {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect a validate method to throw an exception or have some effect instead. Since the return is a boolean, I think it's more reasonable to call the method isValid.

@chemicL
Copy link
Member

chemicL commented Jun 17, 2025

Ah, please also remember to run spring-javaformat:apply before commiting to ensure the formatting checks pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants