CORS Preflight Caching Extends Cross-Origin Policy Decisions

An API operator can remove a cross-origin method from its CORS configuration and still see browsers issue that method without a fresh OPTIONS exchange. The browser may already hold a valid preflight cache entry authorizing the request shape. Until that entry expires or is discarded, the earlier policy decision can continue to suppress a new preflight.

This behavior does not make CORS authorization permanent, and it does not bypass the CORS check on the actual response. It does create an operational interval in which a server-side policy change and browser preflight behavior are not synchronized. Access-Control-Max-Age therefore affects more than request volume: it influences the lifetime of a cached permission decision inside the user agent.

Preflight is a permission check for a request shape

For cross-origin requests that require a CORS preflight, the browser sends an OPTIONS request before the actual request. The preflight carries the requesting origin, the intended method, and, when applicable, the non-safelisted request header names. The server responds with CORS fields that indicate which origins, methods, headers, and credential modes it permits.

A successful exchange can authorize a request such as a PUT from one origin with selected headers. The browser can then send the actual request, subject to the rest of the Fetch processing model.

The key boundary is that CORS is enforced by the user agent. An API endpoint still needs its own authentication, authorization, input validation, and request-integrity controls. A non-browser client can send HTTP requests without participating in browser CORS enforcement. CORS governs whether browser script can make certain cross-origin interactions and receive responses; it is not a replacement for server-side access control.

Preflight also does not cover every cross-origin request. Requests that meet the CORS-safelisted conditions can proceed without a preflight. A security design that assumes every cross-origin operation receives an OPTIONS gate is therefore built on the wrong boundary.

The cache stores protocol decisions, not ordinary responses

The Fetch standard defines a dedicated CORS-preflight cache. Its entries are distinct from ordinary HTTP response caching and are associated with properties including a network partition key, serialized origin, URL, credentials state, method, header name, and expiration lifetime.

That distinction matters during incident response. Clearing or changing ordinary application cache headers does not redefine the semantics of the browser’s preflight cache. The relevant server signal is Access-Control-Max-Age, which supplies a maximum lifetime for successful preflight results. User agents may impose their own caps and may discard entries before their advertised expiration.

A cached preflight result can let a matching later request skip another OPTIONS round trip. Matching is not simply a hostname-wide grant. The cache model incorporates the request origin and URL along with method or header authorization entries and credential conditions. A permission cached for one request context does not become an unrestricted browser-wide CORS exemption.

This makes preflight caching narrower than a generic policy cache, but it is still state carried forward from an earlier server decision.

Revocation is bounded by retained browser state

Suppose an API has allowed DELETE from https://console.example and returns a nonzero Access-Control-Max-Age. A browser completes a successful preflight and stores the result. Minutes later, the API operator removes DELETE from the preflight response configuration.

A matching request from the same browser can still avoid a new preflight while the applicable cache entry remains valid. The server must therefore enforce any real authorization rule on the actual DELETE request itself. Removing a method from CORS configuration is not a dependable mechanism for revoking application permission.

This distinction is especially important when CORS configuration is treated as a security switch during an incident. A shorter preflight lifetime can reduce the period during which old browser decisions remain reusable, but it cannot turn CORS into server authorization. The endpoint must remain capable of rejecting operations that are no longer permitted regardless of browser state.

The actual response is still subject to the CORS check. If the server stops returning an acceptable Access-Control-Allow-Origin value, browser script cannot simply rely on an old preflight entry to read a newly disallowed response. Preflight caching concerns permission to proceed without repeating the preflight; it does not cache a blanket approval for every later response.

That separation can produce a visible operational pattern: the state-changing request reaches the server, but browser script cannot access the response because current response CORS fields fail. For endpoints with side effects, response unreadability does not undo an operation that the server already accepted.

Long cache lifetimes trade latency for policy agility

Preflights add network work. Caching successful results can remove repeated OPTIONS requests and reduce latency for cross-origin APIs that use methods or headers outside the safelisted set. The performance benefit is real, but the lifetime also determines how quickly clients are forced to consult the server again for that preflight decision.

A long Access-Control-Max-Age favors reuse. A short value favors faster convergence after a policy change. Browser-specific maximums mean the server cannot assume that an arbitrarily large advertised value will be retained for exactly that duration. Likewise, the standard permits entries to disappear early, so the server must tolerate a fresh preflight at any time.

This makes the field unsuitable as a precise coordination timer. It is a cache hint within user-agent constraints, not a lease whose full lifetime is guaranteed.

Policy rollout can account for this asymmetry. Expanding a CORS policy is generally compatible with old cached entries because a client that lacks the new permission can issue a fresh preflight when its existing cache does not cover the request. Contracting policy is more delicate because an already cached permission may postpone the next preflight for matching requests.

Where rapid CORS contraction matters operationally, conservative max-age values reduce stale-policy exposure. The stronger control remains server enforcement on the actual resource.

Credentialed requests tighten the response contract

Credentials add another boundary. Under the Fetch CORS protocol, a request whose credentials mode is include cannot use * as the Access-Control-Allow-Origin value. The response needs an explicit matching origin and Access-Control-Allow-Credentials: true for the response to be shared with the requesting script.

CORS-preflight requests themselves use a credentials mode that excludes credentials under the standard’s normal model, while the preflight response can indicate support for the credentialed actual request. This is one reason preflight processing and actual-request processing should not be collapsed into one mental operation.

An origin-reflection implementation deserves particular care. Taking an arbitrary Origin request value and returning it as Access-Control-Allow-Origin can grant cross-origin read access far beyond the intended set, especially when credentialed requests are enabled. An explicit allowlist tied to the application’s actual trust relationships creates a clearer boundary.

When a response varies by request origin, shared HTTP caching also needs correct variance handling. Vary: Origin communicates that the representation or response metadata can differ according to the Origin request field. That ordinary HTTP cache concern is separate from the browser’s dedicated preflight cache, even though both can affect observed CORS behavior.

Side effects expose the limit of browser enforcement

CORS is often described in terms of blocked requests, but the more precise boundary is browser-controlled cross-origin access. Some requests can be sent even when script is not allowed to read the response. Safelisted cross-origin requests are a central example, and servers must not infer authorization from the presence or absence of a preflight.

For state-changing endpoints, server-side controls must decide whether the operation is permitted. Cookie-based authentication may also require cross-site request protections appropriate to the application’s request model. CORS can reduce exposure to hostile browser origins when configured correctly, but it does not establish intent for every request that reaches the server.

The same constraint shapes preflight cache risk. A stale cached preflight decision matters only to the extent that the server still accepts the resulting actual request. If the endpoint has revoked the relevant account permission, disabled the operation, changed its authorization rule, or otherwise rejects the request, the cached browser permission cannot override that server decision.

Conversely, if the server relies on CORS configuration as the sole control for a sensitive operation, a cached preflight exposes the architectural mistake: the browser is carrying a prior cross-origin policy decision while the resource itself lacks an independent authorization boundary.

Preflight lifetime belongs in change planning

CORS configuration changes can be operationally subtle because several mechanisms are involved at once: dedicated preflight state, current response CORS fields, ordinary HTTP caches, credential rules, and application authorization. Treating all of them as one cache produces misleading incident diagnoses.

Access-Control-Max-Age is best treated as a policy-staleness parameter with a performance effect. Its value controls the server’s requested lifetime for reusable preflight results, subject to user-agent limits and early eviction. Shorter lifetimes increase preflight frequency; longer lifetimes increase the period in which an earlier successful decision can suppress a new check.

That trade-off is manageable when the trust boundary is placed correctly. The browser decides whether cross-origin script can proceed under CORS. The server decides whether the actual operation is authorized. Preflight caching can extend the browser’s earlier decision, but it cannot safely carry the server’s authorization responsibility.