Skip to content

Commit 008d07c

Browse files
committed
Update to 0.1.11 conformance tests
1 parent 595942d commit 008d07c

14 files changed

Lines changed: 1007 additions & 94 deletions

File tree

Directory.Packages.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<MicrosoftExtensionsVersion>10.2.0</MicrosoftExtensionsVersion>
88
<!-- Pin the conformance tester Node package version for CI stability.
99
Keep in sync npm install step at ci-build-test.yml -->
10-
<McpConformanceVersion>0.1.10</McpConformanceVersion>
10+
<McpConformanceVersion>0.1.11</McpConformanceVersion>
1111
</PropertyGroup>
1212

1313
<!-- Product dependencies shared -->

src/Common/Polyfills/System/IO/StreamExtensions.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using ModelContextProtocol;
22
using System.Buffers;
33
using System.Runtime.InteropServices;
4-
using System.Text;
54

65
#if !NET
76
namespace System.IO;

src/ModelContextProtocol.AspNetCore/AuthorizationFilterSetup.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using Microsoft.AspNetCore.Authorization;
44
using Microsoft.Extensions.DependencyInjection;
55
using Microsoft.Extensions.Options;
6-
using ModelContextProtocol.Protocol;
76
using ModelContextProtocol.Server;
87

98
namespace ModelContextProtocol.AspNetCore;
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
using Microsoft.AspNetCore.Builder;
2+
using Microsoft.AspNetCore.Http;
3+
using Microsoft.Extensions.Logging;
4+
using Microsoft.Net.Http.Headers;
5+
using System.Net;
6+
using System.Text.Json;
7+
8+
namespace ModelContextProtocol.AspNetCore;
9+
10+
/// <summary>
11+
/// Middleware that provides DNS rebinding protection for MCP servers by validating
12+
/// Host and Origin headers on requests to localhost servers.
13+
/// </summary>
14+
/// <remarks>
15+
/// <para>
16+
/// DNS rebinding attacks can allow malicious websites to bypass browser same-origin policy
17+
/// and make requests to localhost services. This middleware helps protect against such attacks
18+
/// by validating that Host and Origin headers match expected localhost values.
19+
/// </para>
20+
/// <para>
21+
/// Use <see cref="McpApplicationBuilderExtensions.UseMcpDnsRebindingProtection"/> to enable this middleware.
22+
/// </para>
23+
/// </remarks>
24+
/// <remarks>
25+
/// Initializes a new instance of the <see cref="DnsRebindingProtectionMiddleware"/> class.
26+
/// </remarks>
27+
internal sealed partial class DnsRebindingProtectionMiddleware(
28+
RequestDelegate next,
29+
ILogger<DnsRebindingProtectionMiddleware> logger)
30+
{
31+
private readonly RequestDelegate _next = next;
32+
private readonly ILogger<DnsRebindingProtectionMiddleware> _logger = logger;
33+
34+
/// <summary>
35+
/// Processes the HTTP request and validates Host and Origin headers for localhost servers.
36+
/// </summary>
37+
public async Task InvokeAsync(HttpContext context)
38+
{
39+
// Only apply protection to localhost servers
40+
var localEndpoint = context.Connection.LocalIpAddress;
41+
bool isLocalhostServer = localEndpoint is null ||
42+
IPAddress.IsLoopback(localEndpoint) ||
43+
localEndpoint.Equals(IPAddress.IPv6Loopback);
44+
45+
if (isLocalhostServer)
46+
{
47+
// Validate Host header
48+
var host = context.Request.Host.Host;
49+
if (!IsLocalhost(host))
50+
{
51+
LogInvalidHostHeader(host);
52+
await WriteJsonRpcErrorResponseAsync(context, $"Forbidden: Invalid Host header '{host}' for localhost server");
53+
return;
54+
}
55+
56+
// Validate Origin header if present
57+
if (context.Request.Headers.TryGetValue(HeaderNames.Origin, out var originValues) &&
58+
originValues.FirstOrDefault() is string origin &&
59+
Uri.TryCreate(origin, UriKind.Absolute, out var originUri) &&
60+
!IsLocalhost(originUri.Host))
61+
{
62+
LogInvalidOriginHeader(origin);
63+
await WriteJsonRpcErrorResponseAsync(context, $"Forbidden: Invalid Origin header '{origin}' for localhost server");
64+
return;
65+
}
66+
}
67+
68+
await _next(context).ConfigureAwait(false);
69+
}
70+
71+
private static bool IsLocalhost(string host)
72+
{
73+
if (!string.IsNullOrWhiteSpace(host))
74+
{
75+
if (host.Equals("localhost", StringComparison.OrdinalIgnoreCase) ||
76+
host.Equals("[::1]") ||
77+
host.Equals("127.0.0.1"))
78+
{
79+
return true;
80+
}
81+
82+
if (IPAddress.TryParse(host, out var ip))
83+
{
84+
return IPAddress.IsLoopback(ip);
85+
}
86+
}
87+
88+
return false;
89+
}
90+
91+
private static Task WriteJsonRpcErrorResponseAsync(HttpContext context, string message)
92+
{
93+
context.Response.StatusCode = StatusCodes.Status403Forbidden;
94+
context.Response.ContentType = "application/json";
95+
return context.Response.WriteAsync($$"""
96+
{
97+
"jsonrpc": "2.0",
98+
"error":
99+
{
100+
"code": -32000,
101+
"message": "{{JsonEncodedText.Encode(message)}}"
102+
},
103+
"id": null
104+
}
105+
""");
106+
}
107+
108+
[LoggerMessage(Level = LogLevel.Warning, Message = "Rejected request with invalid Host header '{Host}' for localhost server. This may indicate a DNS rebinding attack.")]
109+
private partial void LogInvalidHostHeader(string? host);
110+
111+
[LoggerMessage(Level = LogLevel.Warning, Message = "Rejected request with invalid Origin header '{Origin}' for localhost server. This may indicate a DNS rebinding attack.")]
112+
private partial void LogInvalidOriginHeader(string origin);
113+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using ModelContextProtocol.AspNetCore;
2+
3+
namespace Microsoft.AspNetCore.Builder;
4+
5+
/// <summary>
6+
/// Extension methods for adding MCP middleware to an <see cref="IApplicationBuilder"/>.
7+
/// </summary>
8+
public static class McpApplicationBuilderExtensions
9+
{
10+
/// <summary>
11+
/// Adds DNS rebinding protection middleware for MCP servers running on localhost.
12+
/// </summary>
13+
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
14+
/// <returns>The <see cref="IApplicationBuilder"/> for chaining.</returns>
15+
/// <remarks>
16+
/// <para>
17+
/// This method provides protection against DNS rebinding attacks by validating that both
18+
/// Host and Origin headers (when present) resolve to localhost addresses.
19+
/// </para>
20+
/// <para>
21+
/// DNS rebinding attacks can allow malicious websites to bypass browser same-origin policy and make requests
22+
/// to localhost services. This protection is recommended for any MCP server that binds to localhost.
23+
/// </para>
24+
/// <para>
25+
/// For more information, see the <see href="https://github.com/modelcontextprotocol/typescript-sdk/security/advisories/GHSA-w48q-cv73-mx4w">MCP SDK security advisory</see>.
26+
/// </para>
27+
/// </remarks>
28+
/// <example>
29+
/// <code>
30+
/// var builder = WebApplication.CreateBuilder(args);
31+
/// builder.Services.AddMcpServer().WithHttpTransport();
32+
///
33+
/// var app = builder.Build();
34+
/// app.UseMcpDnsRebindingProtection(); // Add before MapMcp()
35+
/// app.MapMcp();
36+
/// app.Run();
37+
/// </code>
38+
/// </example>
39+
public static IApplicationBuilder UseMcpDnsRebindingProtection(this IApplicationBuilder app)
40+
{
41+
ArgumentNullException.ThrowIfNull(app);
42+
43+
app.UseMiddleware<DnsRebindingProtectionMiddleware>();
44+
45+
return app;
46+
}
47+
}

src/ModelContextProtocol.Core/Authentication/ClientOAuthOptions.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,25 @@ public sealed class ClientOAuthOptions
2323
/// </remarks>
2424
public string? ClientSecret { get; set; }
2525

26+
/// <summary>
27+
/// Gets or sets the private key in PEM format for JWT client assertion (private_key_jwt).
28+
/// </summary>
29+
/// <remarks>
30+
/// When provided along with <see cref="JwtSigningAlgorithm"/>, the client will use JWT client
31+
/// assertion (private_key_jwt) for token endpoint authentication instead of client_secret.
32+
/// This is typically used for machine-to-machine authentication with client_credentials grant.
33+
/// </remarks>
34+
public string? JwtPrivateKeyPem { get; set; }
35+
36+
/// <summary>
37+
/// Gets or sets the signing algorithm for JWT client assertion.
38+
/// </summary>
39+
/// <remarks>
40+
/// Common values include "RS256", "RS384", "RS512", "ES256", "ES384", "ES512".
41+
/// This property is only used when <see cref="JwtPrivateKeyPem"/> is provided.
42+
/// </remarks>
43+
public string? JwtSigningAlgorithm { get; set; }
44+
2645
/// <summary>
2746
/// Gets or sets the HTTPS URL pointing to this client's metadata document.
2847
/// </summary>

0 commit comments

Comments
 (0)