Add usage advice for Sec-
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.
- [ ] At least two implementers are interested (and none opposed): n/a
- [ ] Tests are written and can be reviewed and commented upon at: n/a
- [ ] Implementation bugs are filed: n/a
- [ ] MDN issue is filed: n/a
- [x] The top of this comment includes a clear commit message to use.
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 theSec-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-.
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.)
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?
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.
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.
My understanding is that - for new headers like this - HTTP archive is the benchmark. Are you talking about saving one query to that database?
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?
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.
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.
@annevk I find your claim about SOP confusing. This has very little to do with SOP or the security model. I'm not looking to change the definition of Sec-: there are clear examples of where it is valuable. I'm just seeking to establish clear rationale by which someone might choose to apply it that don't involve reasoning of the form "just in case".
@johannhof The pull request makes the case pretty clearly already. Maybe you disagree. The problem is that denying script the option to request content with forbidden fields forces the use of alternative spellings of the exact same semantics. Consider Sec-CH-DPR. Is there any security reason why a site should not be permitted to request content with a higher DPR than the screen on which the browser assumes it might be displayed?
I don't know that a threat model has been documented, but I don't think that the one you both seem to have settled on is useful. If we consider preflight to be effective in blunting the effectiveness of attacks, and that preflight is included whenever a non-safelisted field is added to a request (at least when using the API; by the way, there is a reason that people always end up asking @annevk to interpret this specification, I had a really hard time working this out; I'm still not confident that I've 100% covered that). There are then no cases where an "attacker"-controlled request appears without first getting permission from the resource.
More fundamentally, if new fields cannot be sent to servers, we have a major problem. New fields are defined all the time. There can be good reasons for browsers to send them. They will appear in form submissions. There's a good reason that the list of forbidden fields includes old fields, because those are more likely to be acted upon by servers that are not aware of CORS. But for new fields defined so long after CORS became ubiquitous, it's silly to insist that a server might act on it in a way that can be exploited.
The threat model I documented is - at least in my view - a clearer one. That is, if a server needs to make a security-relevant decision with confidence that the value of the Sec--prefixed field came from the same place that the credentials did, then it is a good use of a forbidden header.
The threat model I documented is - at least in my view - a clearer one. That is, if a server needs to make a security-relevant decision with confidence that the value of the Sec--prefixed field came from the same place that the credentials did, then it is a good use of a forbidden header.
@martinthomson I don't believe that it is possible to objectively and completely define "needs" and "security-relevant" in this sentence. One origin should just not be able to manipulate state that another origin expects to receive from the user agent, like permission state. I think I agree that there are cases where we can decide to make an explicit exception to this principle, maybe DPR is one of those, but that's the way we should approach this question, not the other way around.
There will always be judgment involved. Our discussion about the proposed Sec--prefixed fields for storage access headers highlights that. But the meaning of those words is clear: if the server depends on the value coming from the browser because it is making a decision that might have unwanted consequences if something other than a browser didn't produce it -- which also implies that there are credentials or something else in the request that is relevant to that decision and that also could only possible come from a browser -- then we have a reason to apply the prefix.
Is there an alternative set of criteria that you would have apply in deciding when Sec- applies?
There will always be judgment involved. Our discussion about the proposed
Sec--prefixed fields for storage access headers highlights that.
Martin, respectfully, it's a bit strange to point to a discussion in which you yourself, citing your proposed model from this PR, question the Sec- prefix, as proof that it's a matter of judgement.
But the meaning of those words is clear: if the server depends on the value coming from the browser because it is making a decision that might have unwanted consequences if something other than a browser didn't produce it -- which also implies that there are credentials or something else in the request that is relevant to that decision and that also could only possible come from a browser -- then we have a reason to apply the prefix.
I'm sorry, how is this clear? How do you objectively define "a decision that might have unwanted consequences" and "relevant to that decision"? With this policy, we would need to maintain a secondary list of both the types of decisions we consider vulnerable and the types of "something else" that we consider impacting those decisions.
Is there an alternative set of criteria that you would have apply in deciding when
Sec-applies?
To quote from my comment just before this one:
One origin should just not be able to manipulate state that another origin expects to receive from the user agent, like permission state.
Again, I believe we can apply judgement in making exceptions to this rule, but we should not discourage usage of Sec- by default.
I understand that there may be valid concerns against Sec-, so let's talk about those and how to mitigate them, but arguing that the SOP is a too strict rule and we should rather follow some rough guideline is really the wrong angle to approach it from.
But for new fields defined so long after CORS became ubiquitous, it's silly to insist that a server might act on it in a way that can be exploited.
Why would this be silly? Isn't it exactly because of the same-origin policy (as augmented by CORS) that servers can rely on not getting headers they don't expect from other origins? And thus can use arbitrary headers (of which we have no knowledge) for their own purpose?