Skip to content

Align Fetch-Metadata CSRF trust with CORS credentials intent - #69345

Open
DeagleGross wants to merge 1 commit into
dotnet:release/11.0from
DeagleGross:deaglegross-redesigned-invention
Open

DeagleGross wants to merge 1 commit into
dotnet:release/11.0from
DeagleGross:deaglegross-redesigned-invention

Conversation

@DeagleGross

@DeagleGross DeagleGross commented Sep 16, 2026

Copy link
Copy Markdown
Member

DefaultCsrfProtection treats a matched CORS policy's allowed origin as a trust signal for Fetch-Metadata-based CSRF checks. Today it does this whenever !policy.AllowAnyOrigin && policy.IsOriginAllowed(origin) is true, without checking policy.SupportsCredentials (the property backing .AllowCredentials()).

This change adds that check, so a CORS policy only expands CSRF trust when it was also configured with .AllowCredentials().

Why is requiring AllowCredentials() fine?

CORS's AllowCredentials() (which sets policy.SupportsCredentials) is the one explicit flag a developer sets when they mean "this cross-origin caller is allowed to send/receive the user's cookies." If a developer did not set it, they were saying "this origin can call me anonymously/publicly," not "this origin can act as the logged-in user."

Since CSRF protection is entirely about authenticated, cookie-bearing actions being triggered from elsewhere, only the credentialed case is actually claiming the trust relationship CSRF protection cares about. Requiring SupportsCredentials before letting a CORS policy grant CSRF trust makes the code check what the developer actually intended, instead of trusting origins that were only ever configured for anonymous/public access.

Docs change if this is merged: dotnet/AspNetCore.Docs#37660

Copilot AI lite review requested due to automatic review settings September 16, 2026 09:49
@DeagleGross DeagleGross changed the title Require CORS AllowCredentials() for Fetch-Metadata CSRF trust Align Fetch-Metadata CSRF trust with CORS credentials intent Sep 16, 2026
@DeagleGross DeagleGross added the Servicing-consider Shiproom approval is required for the issue label Sep 16, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Hi @DeagleGross. Please make sure you've updated the PR description to use the Shiproom Template. Also, make sure this PR is not marked as a draft and is ready-to-merge.

To learn more about how to prepare a servicing PR click here.

@DeagleGross DeagleGross self-assigned this Sep 16, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Existing integration tests must be updated to match the new credential requirement.

Get a fresh assessment by requesting another Copilot review.

Review tier: Lite
Findings: 1 High severity

Open (1)
What changed in this PR

This pull request restricts CORS-derived CSRF trust to policies that explicitly allow credentials.

Changes:

  • Requires SupportsCredentials for CORS-based CSRF trust.
  • Adds credentialed and non-credentialed policy test coverage.
File Summary
src/​DefaultBuilder/​test/​Microsoft.AspNetCore.Tests/​DefaultCsrfProtectionTests.cs Expands coverage across CORS policy configurations.
src/​DefaultBuilder/​src/​Internal/​DefaultCsrfProtection.cs Requires credential support when deriving CSRF trust from CORS. Existing integration tests still expect non-credentialed policies to be trusted.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/DefaultBuilder/src/Internal/DefaultCsrfProtection.cs
@DeagleGross
DeagleGross force-pushed the deaglegross-redesigned-invention branch from 1b006a5 to 4685f52 Compare September 16, 2026 10:03
DeagleGross added a commit to DeagleGross/AspNetCore.Docs that referenced this pull request Sep 16, 2026
Per feedback: the AllowCredentials() requirement for CORS-derived CSRF
trust is unreleased (dotnet/aspnetcore#69345 is still open), so it
shouldn't be documented as a breaking change or a 10->11 migration
concern yet.

- Removed breaking-changes/11/csrf-cors-allowcredentials-required.md
- Reverted the registration in breaking-changes/11/overview.md and toc.yml
- Reverted the migration/100-to-110/includes/security.md CORS bullet
- Removed the breaking-change cross-link from anti-request-forgery.md,
  keeping the AllowCredentials() requirement description, updated CORS
  code sample, and AllowAnyOrigin note

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

@halter73 halter73 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think the functional change is sound, but we should probably add more testing and update some more existing tests to include AllowCredentials().

builder.WebHost.UseTestServer();
builder.Services.AddCors(options =>
options.AddDefaultPolicy(policy => policy.WithOrigins("https://trusted.example.com")));
options.AddDefaultPolicy(policy => policy.WithOrigins("https://trusted.example.com").AllowCredentials()));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we need to addAllowCredentials() to a few more tests to ensure they still cover what they're supposed to. CsrfProtection_PerEndpointEnableCors_OverridesDefaultPolicy now passes even if the default policy is incorrectly selected, and CsrfProtection_PerEndpointDisableCors_FallsThroughToSecFetchSite passes even if [DisableCors] is ignored.

// AllowCredentials() is required, so request is not allowed
var services = BuildCorsServices(o => o.AddDefaultPolicy(p => p.WithOrigins("https://trusted.com")));
var context = CreateContext(origin: "https://trusted.com", secFetchSite: "cross-site", services: services);
Assert.False((await _validator.ValidateAsync(context)).IsAllowed);

@halter73 halter73 Sep 16, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we should cover this through a [FromForm] endpoint in an integration test too: a matching cross-origin request, with and without AllowCredentials(), for both anonymous and authenticated users (which should receive the same CSRF validation result). We should probably also cover an endpoint policy without credentials overriding a credential-enabled default policy for the same origin, so we don't accidentally inherit the default policy's trust.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Servicing-consider Shiproom approval is required for the issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants