Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add usage advice for Sec- #1818

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

martinthomson
Copy link
Contributor

@martinthomson martinthomson commented Mar 31, 2025

There are a lot of examples where the Sec- prefix is used without a lot of consideration for why.

This change is an attempt to articulate why you might want to use this prefix and deny a website the ability to set a value for a header.

Examples of Sec- that make the platform worse include Sec-CH- prefixed headers, which all engage server content negotiation capabilities that sites might be able to use. I would include Sec-Browsing-Topics in this category also, but maybe for more reasons than one.

Examples that are mostly just pointless include Sec-GPC and Sec-Purpose, which both have no security-relevant decision that might be made by a server.

The Sec-Fetch-Dest and Sec-Fetch-Mode headers are good examples of things that would have security consequences if they weren't prefixed. We also have a bunch that are forbidden and not prefixed that make a bunch of sense, like Connection.

I make an argument for Sec-WebSocket-Key in the text, which seems pretty solid to me. I can't make a similar argument for the other websocket headers. Sec-WebSocket-Accept is a response-only header, so the prefix makes no sense other than for naming consistency, which is a bad reason.

Part of the reason for this is that we're seeing a bunch of cargo-culting in the definition of headers. Take device-bound session credentials (https://github.com/w3c/webappsec-dbsc), which defines a response header called Sec-Session-Registration. There, the reason appears to be consistency with request header naming, but it's not clear that the request headers themselves need a Sec- prefix either.


Preview | Diff

There are a lot of examples where the `Sec-` prefix is used without a lot of consideration for why.

This change is an attempt to articulate why you might want to use this prefix and deny a website the ability to set a value for a header.

Examples of `Sec-` that make the platform worse include `Sec-CH-` prefixed headers, which all engage server content negotiation capabilities that sites might be able to use.  I would include `Sec-Browsing-Topics` in this category also, but maybe for more reasons than one.

Examples that are mostly just pointless include `Sec-GPC` and `Sec-Purpose`, which both have no security-relevant decision that might be made by a server.

The `Sec-Fetch-Dest` and `Sec-Fetch-Mode` headers are good examples of things that would have security consequences if they weren't prefixed.  We also have a bunch that are forbidden and not prefixed that make a bunch of sense, like `Connection`.

I make an argument for `Sec-WebSocket-Key` in the text, which seems pretty solid to me. I can't make a similar argument for the other websocket headers.  `Sec-WebSocket-Accept` is a response-only header, so the prefix makes no sense other than for naming consistency, which is a bad reason.

Part of the reason for this is that we're seeing a bunch of cargo-culting in the definition of headers.  Take device-bound session credentials (https://github.com/w3c/webappsec-dbsc), which defines a response header called `Sec-Session-Registration`.  There, the reason appears to be consistency with request header naming, but it's not clear that the request headers themselves need a `Sec-` prefix either.
@domenic
Copy link
Member

domenic commented Mar 31, 2025

I am interested in your example

The [Sec-Purpose](https://whatpr.org/fetch/1818.html#http-sec-purpose) field tells a server that a request is speculative. A server might choose to avoid triggering side-effects while processing such a request, such as suppressing the recording of page view metrics. Making this a forbidden request-header has no security-relevant purpose and the Sec- prefix is therefore unnecessary.

(To double-check, this is not an example of the advice directly above it about CORS preflights, right? It's just coincidentally right after that, as something else you don't agree with.)

I think this falls into a general category where servers are better served by getting accurate information about the purpose of a request, but indeed getting inaccurate information isn't a security problem. (At least, not a security problem more serious than a DOS.)

In such cases, I've advised spec writers that using Sec- is a reasonable default. That is, if there's no compelling use case for letting fetch() fake a fetch-involving web platform feature, and servers benefit from mostly getting accurate information about such fetches, then we should default to using Sec- to help servers out.

A recent example is our design of Sec-Speculation-Tags, where we say

We've proposed using the Sec- prefix since we do not believe there are use cases for allowing web developers to manipulate these headers from JavaScript via a service worker, or set them with fetch() calls. Allowing such manipulation would not necessarily break anything, but it is probably simpler for web developers if they can always trust that a Sec-Speculation-Tags header comes from an actual speculation rules-initiated request.

There's also somewhat of a self-reinforcing argument here, because it could be confusing for servers to receive a request with Speculation-Tags but not Sec-Purpose. That is, since Sec-Purpose uses Sec-, even if we believe that was not necessary, it's probably better to have other features that build on Sec-Purpose also use Sec-.

@martinthomson
Copy link
Contributor Author

martinthomson commented Mar 31, 2025

I thought that that would attract comment :)

To double-check, this is not an example of the advice directly above it about CORS preflights, right? It's just coincidentially right after that [...]

Yeah, I struggled with the transition there. It's coincidental positioning only.

I have heard a number of people who say that they received advice about the Sec- prefix of the form that you describe. And there's a definite pattern of cargo-culting or at least naming consistency being used to justify more of it. That does real harm with things like Sec-UA-etc... where apps can't choose to hook into the capabilities that the header might otherwise enable.

Your example of speculation is one where the harm isn't obvious. Why would an app want to trigger a fetch marked as prefetch? If you can't imagine a reason, there's a tendency to slap a Sec- on and move on. But that is just a failure of imagination. We just can't imagine a case where someone might choose to fetch that way. I can easily imagine a site wanting to prefetch something on the basis of JS code deciding that a particular navigation is imminent. Why not also for speculative fetches as well? I can well imagine that the number of cases where the user agent is in a better position to drive that, but why deny apps that option?

(Of course, the need for a preflight could make the prefetch too slow to be useful, but that's a separate problem.)

@johannhof
Copy link
Member

Martin, thanks for writing this up - Another reason that we liked using Sec- for Storage-Access Headers was that the Sec- namespace is automatically reserved by virtue of the entire prefix being forbidden, making conflicts with existing custom application headers impossible.

Do you have any thoughts on that and should we consider this in advice we give to browser developers?

@martinthomson
Copy link
Contributor Author

I don't know what you mean by reserved here.

If you mean reserved for this purpose (and not some other purpose), that is why we have IANA registries. The storage access headers appear to be registered, so that's probably not it... (That's not true for a few other headers that others have started to use. Including Sec-Fetch-*, which is not great...calling @mikewest.)

Reserving a header for the exclusive use of a user agent is somewhat appealing as a user agent developer. You say "mine" and that's the end of the story. No further thought. But adopting that position as a default denies sites the option to use the header.

That's probably OK for Sec-WebSocket-Key, which is really only for browsers to use anyway. Sec-CH-* doesn't meet that bar, Sec-Purpose doesn't, and nor does the Sec-Fetch-Storage-Access (which is a really, really long header name to be sending on EVERY request, by the way). If a site believes that it has storage access and it doesn't or vice versa, then it might not work properly, but that's on the fool that sets the header to the wrong value. There's no security-relevant decision riding on it being correct; those decisions are made based on the content of cookies.

@johannhof
Copy link
Member

I mean it's impossible to have prior usage of this header on the web, which avoids the pain of finding out whether you're going to break someone's site with it.

@martinthomson
Copy link
Contributor Author

My understanding is that - for new headers like this - HTTP archive is the benchmark. Are you talking about saving one query to that database?

martinthomson and others added 2 commits April 1, 2025 20:04
Co-authored-by: Mike West <[email protected]>
Co-authored-by: Mike West <[email protected]>
Co-authored-by: Simon Pieters <[email protected]>
@johannhof
Copy link
Member

My understanding is that - for new headers like this - HTTP archive is the benchmark. Are you talking about saving one query to that database?

HTTP Archive covers public traffic, i.e. non-logged in scenarios and non-enterprise. This is a great suggestion but I'm not sure it's sufficient, it's certainly not the same as "literally guaranteed to have no prior usage on the Web". Given that we have a simple mechanism that can make that guarantee for both browsers and servers, why not use it?

@mnot
Copy link
Member

mnot commented Apr 3, 2025

Is avoiding breaking even a single site really a reasonable goal here, considering that a site can also change its private use if there is a clash? Many new headers have been introduced without a Sec- prefix over the years and haven't caused significant breakage.

Copy link
Member

@annevk annevk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are going to redefine what it means to extend the same-origin policy we should not do that here.

requests</a> include [:Access-Control-Request-Method:], which is [=forbidden
request-headers|forbidden=]. Any <a for=/>headers</a> that a fetch caller sets will not be set on a
<a>CORS-preflight request</a> made by an honest user agent; instead, these are listed in
[:Access-Control-Request-Headers:].
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is a compelling example as these were introduced prior to the introduction of the prefix.

<a for=/>request</a> is speculative. A server might choose to avoid triggering side-effects while
processing such a request, such as suppressing the recording of page view metrics. Making this a
<a>forbidden request-header</a> has no security-relevant purpose and the `<code>Sec-</code>` prefix
is therefore unnecessary.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a redefinition of the threat model. See w3c/resource-hints#74 (comment) for a discussion of this header.

Sending new headers across origin without preflight and without a Sec- prefix is an extension of the same-origin policy.

@johannhof
Copy link
Member

I agree with Anne's point about sending new headers across origins being an SOP issue and I like that this formulates a clear objective rule to follow here.

I think it also makes it more clear to me what made me uneasy about this PR - it tries to impose a subjective decision upon browser developers to take their best guess on whether or not their header could be security relevant - with a bias towards not adding the Sec- prefix, i.e. the less secure path, which seems like the inverse of how these kinds of security-related decisions should be made.

@martinthomson it would be good if you could clearly formulate your reasons for wanting to stop people from "cargo-culting" on Sec-. Is it about the extra bytes? If that is a concern, we should have a fundamental and data-informed discussion about the impact of larger header names and how we could define clear rules to keep them smaller, such as deprecating Sec- in favor of adding new headers to the forbidden request header list. As I pointed out, I believe there is real value in having a separate header namespace reserved for user agents, but I'm not married to that idea if there is value in abandoning it.

From a security standpoint, there should be clear rules though, and I support the threat model that Anne puts forward here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

7 participants