Skip to content

[ResponseCaching] Correctly handle Vary: * in delimited or multi-value headers (RFC 9111) - #69210

Merged
cincuranet merged 4 commits into
dotnet:mainfrom
Sadik00789:fix/vary-star-caching
Sep 11, 2026
Merged

[ResponseCaching] Correctly handle Vary: * in delimited or multi-value headers (RFC 9111)#69210
cincuranet merged 4 commits into
dotnet:mainfrom
Sadik00789:fix/vary-star-caching

Conversation

@Sadik00789

@Sadik00789 Sadik00789 commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Fixes #69192.

Per RFC 9111 § 4.1, a response containing * anywhere as a member of the Vary header field MUST NOT be used to satisfy a subsequent request. Previously, ResponseCachingPolicyProvider performed an exact match on varyHeader.Count == 1 && string.Equals(varyHeader, "*", ...) which failed when:

  1. Downstream middleware (e.g., Response Compression) appended headers, resulting in varyHeader.Count > 1.
  2. The Vary header contained comma-delimited tokens (e.g., *, Accept-Encoding).

Changes

  • Output Caching Audit: Verified that OutputCaching determines vary behavior via explicit cache key rules (IOutputCachePolicy, CacheVaryByRules) rather than reading context.Response.Headers.Vary. No changes required there.
  • Response Caching:
    • Replaced the exact-match check with span-based token inspection using MemoryExtensions.Split(',') on rawHeader.AsSpan().
    • Iterated over StringValues directly by index to avoid string.Join allocations.
    • Trimmed each segment span and verified equality via SequenceEqual("*") without heap allocations.
  • Tests:
    • Expanded unit test IsResponseCacheable_VaryHeaderByStar_NotAllowed to a [Theory] covering single strings, multi-entry StringValues, and delimited values with spaces.
    • Added unit test asserting valid headers without * remain cacheable.
    • Added an integration test in ResponseCachingMiddlewareTests with downstream appended headers to verify subsequent requests are not served from cache.

Customer Impact

Prevents incorrectly caching responses that vary by * when response compression or other middleware appends headers to Vary.

Regression?

No.

Risk

Low. Restricts caching behavior to strictly adhere to RFC 9111 without allocating heap strings.

- Comply with RFC 9111 § 4.1 by inspecting each delimited token within Vary headers.
- Iterate StringValues directly with StringTokenizer to prevent string allocations on hot paths.
- Add unit and integration tests covering multi-entry and delimited '*' headers.

Fixes dotnet#69192
@github-actions github-actions Bot added the area-middleware Includes: URL rewrite, redirect, response cache/compression, session, and other general middlewares label Sep 10, 2026
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Sep 10, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Thanks for your PR, @Sadik00789. Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

@Sadik00789

Copy link
Copy Markdown
Contributor Author

@dotnet-policy-service agree

Comment on lines +112 to +120
var tokenizer = new StringTokenizer(rawHeader, HeaderDelimiters);
foreach (var segment in tokenizer)
{
if (segment.Trim().Equals("*", StringComparison.Ordinal))
{
context.Logger.ResponseWithVaryStarNotCacheable();
return false;
}
}

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.

Any reason to not use a span here? Something like:

var rawHeader = varyHeader[i].AsSpan();
if (rawHeader.Length == 0)
{
    continue;
}

foreach (var segment in rawHeader.Split(','))
{
    if (rawHeader[segment].Trim().SequenceEquals("*"))
    {
        context.Logger.ResponseWithVaryStarNotCacheable();
        return false;
    }
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated to use Span.Split(',') and zero-allocation span checks, and cleaned up the delimiters array. Thanks for the suggestion!

@Youssef1313 Youssef1313 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.

What about OutputCaching?

@Sadik00789

Copy link
Copy Markdown
Contributor Author

During the initial audit, I checked src/Middleware/OutputCaching/ and found that OutputCaching does not inspect context.Response.Headers.Vary when determining cache eligibility (IOutputCachePolicy / AllowCacheStorage). Cache vary behavior there is driven strictly by its own policy rules (CacheVaryByRules / OutputCacheKeyProvider).

Did you want OutputCacheMiddleware / DefaultOutputCachePolicy to also inspect downstream response headers and refuse to cache if Vary: * is present, or should CacheVaryByRules handle * explicitly? If so, I'm happy to tackle that in this PR or a follow-up issue.

Comment thread src/Middleware/ResponseCaching/src/ResponseCachingPolicyProvider.cs Outdated
Comment thread src/Middleware/ResponseCaching/src/ResponseCachingPolicyProvider.cs Outdated
Sadik00789 and others added 2 commits September 11, 2026 16:33
…er.cs

Co-authored-by: Jiri Cincura ↹ <jiri@cincura.net>
…er.cs

Co-authored-by: Jiri Cincura ↹ <jiri@cincura.net>
@Sadik00789

Copy link
Copy Markdown
Contributor Author

Applied the suggested changes (IsEmpty and is ['*'] pattern matching). Ready for another look!

@cincuranet cincuranet 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.

LGTM. @Youssef1313 do you want to give it a look?

@cincuranet
cincuranet enabled auto-merge (squash) September 11, 2026 10:53
@cincuranet
cincuranet merged commit 04421bc into dotnet:main Sep 11, 2026
27 checks passed
@Sadik00789
Sadik00789 deleted the fix/vary-star-caching branch September 11, 2026 12:10
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 12.0-preview1 milestone Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-middleware Includes: URL rewrite, redirect, response cache/compression, session, and other general middlewares community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect cache handling with Vary: * with response compression

3 participants