Skip to content

Add context-aware CORS policy to enhance security#1609

Open
g2vinay wants to merge 3 commits intomicrosoft:mainfrom
g2vinay:update-cors-policy
Open

Add context-aware CORS policy to enhance security#1609
g2vinay wants to merge 3 commits intomicrosoft:mainfrom
g2vinay:update-cors-policy

Conversation

@g2vinay
Copy link
Contributor

@g2vinay g2vinay commented Jan 29, 2026

Add context-aware CORS policy for HTTP transport mode

  • Development mode (unauthenticated): Restricts to localhost origins only to prevent CSRF attacks

    • Allows localhost/127.0.0.1/[::1] with any port (supports MCP Inspector, custom ports)
    • Protects against cross-site request forgery when --dangerously-disable-http-incoming-auth is used
    • Prevents malicious websites from accessing developer's Azure credentials via localhost endpoints
  • Production mode (authenticated): Allows all origins safely

    • JWT Bearer authentication validates all requests regardless of origin
    • Required for MCP clients (GitHub Copilot in VS Code/Codespaces) from various origins
    • CORS is a browser mechanism; authenticated API doesn't need origin restrictions

Replaces permissive 'AllowAll' policy with context-aware 'McpCorsPolicy'
Implements ConfigureCors() method shared by both authenticated and unauthenticated HTTP hosts

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a context-aware CORS policy to enhance security for the HTTP transport mode. The policy behaves differently based on the authentication mode: in development mode (unauthenticated), it restricts requests to localhost origins only to prevent CSRF attacks, while in production mode (authenticated), it allows all origins since JWT Bearer authentication validates all requests.

Changes:

  • Introduced a new ConfigureCors() method that configures CORS policy based on the DangerouslyDisableHttpIncomingAuth flag
  • Replaced the permissive "AllowAll" CORS policy with a context-aware "McpCorsPolicy"
  • Updated both CreateHttpHost and CreateIncomingAuthDisabledHttpHost methods to use the new shared CORS configuration

Comment on lines +684 to +696
{
if (Uri.TryCreate(origin, UriKind.Absolute, out var uri))
{
// Allow localhost and 127.0.0.1 with any port
return uri.Host == "localhost" ||
uri.Host == "127.0.0.1" ||
uri.Host == "[::1]"; // IPv6 loopback
}
return false;
})
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials(); // Required when using SetIsOriginAllowed
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

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

The lambda expression passed to SetIsOriginAllowed has inconsistent indentation. The opening brace on line 684 is indented with extra spaces compared to the standard indentation pattern used in the rest of the codebase. This should be aligned consistently with the method call on line 683.

Suggested change
{
if (Uri.TryCreate(origin, UriKind.Absolute, out var uri))
{
// Allow localhost and 127.0.0.1 with any port
return uri.Host == "localhost" ||
uri.Host == "127.0.0.1" ||
uri.Host == "[::1]"; // IPv6 loopback
}
return false;
})
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials(); // Required when using SetIsOriginAllowed
{
if (Uri.TryCreate(origin, UriKind.Absolute, out var uri))
{
// Allow localhost and 127.0.0.1 with any port
return uri.Host == "localhost" ||
uri.Host == "127.0.0.1" ||
uri.Host == "[::1]"; // IPv6 loopback
}
return false;
})
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials(); // Required when using SetIsOriginAllowed

Copilot uses AI. Check for mistakes.
Comment on lines +892 to +895
.AddHttpClientInstrumentation(o =>
o.FilterHttpRequestMessage = ShouldInstrumentHttpRequest)
.AddAzureMonitorTraceExporter(exporterOptions =>
exporterOptions.ConnectionString = connectionString)
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

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

These formatting changes to the AddHttpClientInstrumentation and AddAzureMonitorTraceExporter method calls appear to be unrelated to the CORS policy changes described in the PR. These changes should either be removed or explained in the PR description if they are intentional refactoring.

Suggested change
.AddHttpClientInstrumentation(o =>
o.FilterHttpRequestMessage = ShouldInstrumentHttpRequest)
.AddAzureMonitorTraceExporter(exporterOptions =>
exporterOptions.ConnectionString = connectionString)
// Configure HTTP client instrumentation to filter out specific requests (e.g., App Insights ingestion, Azure SDK duplicates).
.AddHttpClientInstrumentation(o =>
{
o.FilterHttpRequestMessage = ShouldInstrumentHttpRequest;
})
// Configure Azure Monitor exporter with the connection string from environment configuration.
.AddAzureMonitorTraceExporter(exporterOptions =>
{
exporterOptions.ConnectionString = connectionString;
})

Copilot uses AI. Check for mistakes.
Comment on lines +669 to +712
private static void ConfigureCors(IServiceCollection services, ServiceStartOptions serverOptions)
{
services.AddCors(options =>
{
options.AddPolicy("McpCorsPolicy", policy =>
{
// In unauthenticated mode (development), restrict to localhost by default
// Allows localhost with any port to support various development scenarios:
// - Port 1031: Default HTTP mode (launchSettings.json)
// - Port 5008: SSE mode default
// - Port 5173: MCP Inspector tool
// - Custom ports: User-specified via ASPNETCORE_URLS
if (serverOptions.DangerouslyDisableHttpIncomingAuth)
{
policy.SetIsOriginAllowed(origin =>
{
if (Uri.TryCreate(origin, UriKind.Absolute, out var uri))
{
// Allow localhost and 127.0.0.1 with any port
return uri.Host == "localhost" ||
uri.Host == "127.0.0.1" ||
uri.Host == "[::1]"; // IPv6 loopback
}
return false;
})
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials(); // Required when using SetIsOriginAllowed
}
// In authenticated mode (production), allow all origins by default
// This is safe because:
// 1. Authentication (JWT Bearer) validates all requests regardless of origin
// 2. CORS is a browser security mechanism, not a server security feature
// 3. MCP clients (GitHub Copilot in VS Code/Codespaces) need to connect from various origins
// 4. The server still enforces authentication and authorization on every request
else
{
policy.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
}
});
});
}
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

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

The new ConfigureCors method introduces important security logic that restricts localhost origins in development mode but lacks unit test coverage. Given that the ServiceStartCommandTests.cs file exists with tests for other configuration options, tests should be added to verify:

  1. Localhost origins (localhost, 127.0.0.1, [::1]) are allowed when DangerouslyDisableHttpIncomingAuth is true
  2. Non-localhost origins are rejected when DangerouslyDisableHttpIncomingAuth is true
  3. All origins are allowed when DangerouslyDisableHttpIncomingAuth is false
  4. AllowCredentials is set correctly in development mode

Copilot uses AI. Check for mistakes.
})
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials(); // Required when using SetIsOriginAllowed
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

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

The comment states that AllowCredentials is "Required when using SetIsOriginAllowed", but this is not accurate. While AllowCredentials is commonly used with SetIsOriginAllowed for authenticated scenarios, it is not technically required by the API. The comment should clarify that it's needed to allow browsers to send credentials (cookies, authorization headers) in cross-origin requests, which is important for the development mode scenario.

Suggested change
.AllowCredentials(); // Required when using SetIsOriginAllowed
.AllowCredentials(); // Allows browsers to send credentials (cookies, auth headers) with cross-origin requests when using this localhost-only policy in development

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Untriaged

Development

Successfully merging this pull request may close these issues.

1 participant