Skip to content

OutputCachePolicyBuilder.With re-evaluates its predicate across policy phases #69720

Description

@cincuranet

Describe the bug

OutputCachePolicyBuilder.With(...) wraps the complete built policy, including DefaultPolicy, in PredicatePolicy. PredicatePolicy evaluates the predicate separately during the request, cache-serve, and response phases.

If the predicate is true when the request enters the output cache middleware but becomes false while the endpoint executes, mutations made during CacheRequestAsync remain active while ServeResponseAsync is skipped. In particular, DefaultPolicy can enable cache storage during the request phase without getting an opportunity to reject the resulting response. The response can then be stored despite having a Set-Cookie header, an authenticated user, or a non-200 status code.

Expected behavior

A With(...) requirement should have one applicability result for the lifetime of a request. The result established during CacheRequestAsync should also control the cache-serve and response phases.

Steps to reproduce

Run this minimal app:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOutputCache();

var app = builder.Build();
var executions = 0;

app.UseOutputCache();

app.MapGet("/", (HttpContext context) =>
{
    context.Items["EndpointExecuted"] = true;
    context.Response.Headers["X-Execution"] = (++executions).ToString();
    context.Response.Cookies.Append("repro", "value");
    return Results.Redirect("/target");
}).CacheOutput(policy => policy.With(context =>
    !context.HttpContext.Items.ContainsKey("EndpointExecuted")));

app.Run();

Request / twice without following the redirect:

curl -i http://localhost:5000/
curl -i http://localhost:5000/

Both responses are 302, contain Set-Cookie: repro=value and X-Execution: 1, and the second response has an Age header. This shows that the second response was served from the cache without executing the endpoint again.

The predicate is initially true, so DefaultPolicy.CacheRequestAsync enables storage. The endpoint then changes the predicate result to false, causing PredicatePolicy to skip DefaultPolicy.ServeResponseAsync and its response exclusions.

Activity

  1. github-actions commented on Oct 8, 2026

    @github-actions
    Contributor

    Triage Summary

    Area: area-middleware (Output caching: OutputCachePolicyBuilder.With / PredicatePolicy)
    Type: Bug (the predicate is re-evaluated per phase, so the result can change between CacheRequestAsync and ServeResponseAsync)

    Potential Duplicates

    • None found

    Notes

    Generated by Issue Triage Agent for dotnet/aspnetcore for #69720 · copilot · auto · 25.5 AIC · ⌖ 1.02 AIC · ⊞ 31.2K · ◷

  2. added
    area-middlewareIncludes: URL rewrite, redirect, response cache/compression, session, and other general middlewares
    on Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-middlewareIncludes: URL rewrite, redirect, response cache/compression, session, and other general middlewares

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions