Skip to content

Security Advisory: Vary: * reuse prohibition bypassed by whitespace/list forms and prototype field names #57

Description

@waydeshi

Summary

Attribute Value
Vendor / Org kornelski
Product http-cache-semantics
Component _varyMatches (index.js)
Affected Versions <= 4.1.1 (present on main)
Severity Medium
CVSS 3.1 Score 5.9 (Medium)
CVSS 3.1 Vector CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N
CWE CWE-436 (Interpretation Conflict)
Affected File index.js:293-304

Description

An interpretation conflict in _varyMatches of kornelski/http-cache-semantics <= 4.1.1 lets a stored response the origin marked Vary: * (never reuse) be served to a different client whenever the header is written in a whitespace or trailing-comma form such as "* ", " *", or "*,", or when the Vary field name resolves through the JavaScript prototype chain (e.g. Vary: constructor). The Vary: * block is an exact string equality check (this._resHeaders.vary === '*'), so any value that is semantically * but not byte-identical falls through to per-field matching. For the whitespace/comma forms the split produces a * (or empty) field whose header value is undefined on both sides and therefore compares equal; for constructor both req.headers and the stored map inherit the same prototype value and also compare equal. In each case the vary check returns true and the origin's "do not reuse" instruction is ignored.


Impact

A shared cache using this library can serve a response the origin explicitly marked Vary: * (or Vary: <field>) to a client for whom it was not computed, when the origin uses a non-canonical whitespace form or a header name that collides with an Object.prototype member. Vary: * is the origin's strongest "never reuse this across requests" signal, commonly emitted for per-user content. The result is cross-client response disclosure decided by attacker-independent but origin-reachable header formatting.


Remediation

Recommended Fix

Normalize before the * test: split and trim first, then treat any field equal to * as the wildcard (if (fields.includes('*')) return false;). Guard the per-field lookup with Object.prototype.hasOwnProperty.call(req.headers, name) on both operands, or use a null-prototype map, so inherited names cannot match vacuously. This restores the documented Vary semantics the library already claims to implement ("It's aware of many tricky details such as the Vary header").

Workaround

Consumers can canonicalize Vary to a bare * before storing, and reject responses whose Vary field names are not simple tokens.


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