Skip to content

Security Advisory: max-stale request directive overrides shared-cache reuse prohibitions (Set-Cookie, proxy-revalidate) #56

Description

@waydeshi

Summary

Attribute Value
Vendor / Org kornelski
Product http-cache-semantics
Component satisfiesWithoutRevalidation / maxAge (index.js)
Affected Versions <= 4.1.1 (present on main)
Severity High
CVSS 3.1 Score 7.5 (High)
CVSS 3.1 Vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
CWE CWE-525 (Use of Web Browser Cache Containing Sensitive Information) / CWE-639
Affected File index.js:250-259, index.js:396-403

Description

A max-stale handling flaw in satisfiesWithoutRevalidation of kornelski/http-cache-semantics <= 4.1.1 allows an unauthenticated remote client to be served a shared-cache entry the library had deliberately marked non-reusable, including another user's Set-Cookie, by sending a single Cache-Control: max-stale request directive. The library sets maxAge() to 0 for a shared response that carries Set-Cookie without public, and for a proxy-revalidate response, specifically to stop that content being reused across users. The stale-serving branch only checks must-revalidate before honoring max-stale, so it re-serves those zero-lifetime entries anyway. An attacker who requests the same URL with a large max-stale receives the cached response body and headers that were computed for a different user.


Impact

In a shared/proxy cache built on this library, any unauthenticated client can retrieve a cached response the library had flagged as non-shareable, including a Set-Cookie header that establishes another user's session. This is a cross-user confidentiality break driven entirely by an attacker-controlled request header. The exposure requires the shared cache to retain the entry and consult satisfiesWithoutRevalidation at read time, which is the documented usage pattern in the README.


Remediation

Recommended Fix

When maxAge() is zeroed for a security reason (shared Set-Cookie without public, proxy-revalidate, no-cache), the stale-serving branch must not be reachable via max-stale. Track those prohibitions separately from ordinary expiry and gate the allowsStale branch on them, e.g. refuse max-stale when this._isShared && this._resHeaders['set-cookie'] && !this._rescc.public, or when this._rescc['proxy-revalidate']. This keeps the existing maxAge()===0 guarantee intact for consumers who use satisfiesWithoutRevalidation as documented.

Workaround

Consumers can strip Set-Cookie before caching and refuse to store proxy-revalidate responses, or ignore client max-stale in a shared deployment.


References

Activity

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions