verifyfirst

A status code · one-of-several-values-for-one-name · documented

Two field lines with one name collapse into one value, and clients disagree about which

NS-076 documentedone-of-several-values-for-one-name

Two field lines with one name collapse into one value, and clients disagree about which

reads as
`resp.headers['X-Frame-Options'] == 'DENY'` and the assertion passes. Conclusion drawn: the response carries the header the policy requires.
actually
The response carried the field twice, DENY then ALLOWALL, because two layers each added it. RFC 9110 permits recombination only in limited circumstances: a sender MUST NOT generate multiple field lines with the same name in a message unless that field's definition allows multiple field line values to be recombined as a comma-separated list. Senders do it anyway, and recipients then differ. Some return the first value, some the joined list, some keep both. X-Frame-Options is not a list-valued field, so a browser receiving it twice with conflicting values has no defined behaviour to fall back on.
blind because
A header dictionary maps one name to one string. It has no way to represent 'this name appeared twice with contradicting values', so the single reading that satisfies the assertion is the only reading the instrument can produce.
the check
Read the field lines rather than the parsed mapping: `curl -sD - -o /dev/null URL | grep -ci '^x-frame-options:'`, and treat any count above one as a failure. Observed against a local server sending X-Frame-Options twice (DENY, ALLOWALL) and Cache-Control twice (no-store, max-age=31536000): curl printed both lines each time; Python's urllib.request returned 'DENY' and 'no-store' from `headers[name]` while `headers.get_all` returned both values; `http.client.getheader` returned the joined 'no-store, max-age=31536000'; Node 20.20.2 returned the joined 'DENY, ALLOWALL'. One response, three clients, three different answers.
cost of missing
A security-header audit passes on a response no browser will honour, and a caching audit reads no-store while a shared cache reads the joined value and stores the response for a year.
mitigation
Assert on the count of field lines as well as on the value, and use the client API that preserves multiplicity (`get_all`, `rawHeaders`) wherever a header carries policy.
generalises to
Every flattening of a multi-valued source into a scalar: repeated query parameters, environment variables set twice, merged configuration layers, duplicate rows collapsed by a join.
source
rfc-editor.org

Reported as

Others this instrument misses

plain text · full registry