Set safer site defaults for today and tomorrow
Hello, my name is Rowan, and I want to tell you about safer defaults you can set on your site today and how we are trying to make those the future defaults for the web as a whole.
One of the things that makes the web an incredible platform is the ability to pull in all the content and services you need to rapidly build amazing experiences.
Use a video?
Just embedd it.
Cleaner font?
Just link it.
A little cat to chase your mouse cursor around the screen,?
Just pull in the JavaScript.
Truly you can have it all.
The thing is inviting things onto your homepage is much like inviting people into your actual home.
You're giving them access to this environment, which means you want trust and visibility about what you have enabled them to do.
Now historically, the web has been a bit of an oversharer.
And by that, I mean, functionality has tended to be open by default, but it's been your responsibility as a site developer to lock down anything that might be sensitive.
The thing, is a complete lockdown where you share nothing with anyone is not only a nearly impossible moving target, it's also rarely the outcome you're after.
And what we're really looking for is balance.
We want that principle of least privilege where the web is safe by default, but when we do want to share, it's easy to extend a personalized invitation for just the access that's needed.
Now, with that, here's the much catchier title that I should have gone with for this session.
Eight top tips for a safer site.
And why eight?
Because we are going to keep them bite-sized.
Straight into the ist then at number eight.
Let's start with an easy one where the default has already been updated.
The canonically misspelled referer and referrer policy.
Now the header may be missing in our character, but what was not missing was information about the full page the browser just for.
This meant that each time your browser sent a request, it was attaching the referrer header and sending that to sub resources or the next page you navigated to.
The result being you could expose all kinds of information to third parties from something innocuous, like the artist you were looking at, to language support, sensitive content, or even functional content, like a token that could be stolen.
Now we also have referrer policy, a response header that sites consent to control what the browser would send in the refer header.
So now you have that slider you can use, all the way from no refer to sending the whole thing all of the time.
The change we made back in Chrome 85 was to set our default policy to 'strict-origin-when-cross-origin'.
And that default is similar across most browsers.
What this means is the same origin requests get the full referrer value, but any cross origin request only sees the other origin in the header.
And there you go, a safer default already in place.
And if you need to tweak it or learn about some of the alternatives, there is a best practice article up on web.Dev.
On to number seven then-it's user agent.
And we're sticking with our theme of HTTP request headers that need a little bit of cleaning up.
Here's the user agent string on my phone at the moment.
As you're likely aware, there's a bunch of historical baggage in this format and it tends to be a bit of a pain to parse when you're accounting for different browsers and devices.
And the data that's in there does have all kinds of valid uses, but it's also a source of highly identifying information about my current environment.
So we're going to be reducing the amount of information in Chrome's user agent string by providing fixed values for the platform version, mobile device type, and for Chrome version.
We remove the more sensitive data, but in a backwards compatible way.
So sites that are checking the user agent for the mobile bit or the OS type they'll continue to work.
But if you need that more detailed information, it's not lost, but you do have to ask for it.
And that's where user agent client hits come.
If you want one of those extended values, you need to send the accept-CH response header with the hint that you want, for example, the model, and then subsequent browser requests you get that value back.
Now there is a fair bit more on this topic.
Like there's an origin trial for testing a new format or demos for the change-they are all up on this user agent reduction page.
The key lesson for you is we've got a safer default user agent string coming.
And if you rely on platform version, device type, or full Chrome build, then you need to migrate to user-agent client hints.
Okay.
Coming in at number six, it is MIME-sniffing and frankly, it sounds terrifying.
So you'll be glad to know this tip is about putting a stop to it.
So, what is it?
Well, in some situations your browser might try to guess or sniff the MIME-type of a file for resource.
Now, an attacker might take advantage of this by uploading something to the server that looks like an image that actually contains HTML and lets them execute JavaScript.
You can put a stop to this by sending the response header X-Content-Type-Options with the value, nosniff.
And you can add this to pretty much every resource that you serve.
Just make sure that you also specify the correct Content-Type header for the resource as well.
For a bit more info, we've got a whole security headers cheat sheet on web.dev that covers this and more.
So, boom default done.
Let's keep going.
Number five is fetch metadata, and this turn things around a little bit, instead of removing data, the browser is going to give us more information about the context of the request so that we can be a bit safer in processing it.
This comes in the form of four request headers.
Sec-Fetch-Site is going to tell you about the context of the request.
So same origin, same site, cross origin, or none.
Then Sec-Fetch-Mode is going to give you the mode of the request, as in CORS, No CORS and navigate, and so on.
Sec-Fetch-Dest refers to the request destination, which is actually the type of resource being requested, like script, font, image, iframe and a few others.
And finally Sec-Fetch-User, which is a boolean that is only added and set to true when the request is a navigation that was triggered by the user.
So given this information, you can add some checks into your routing configuration to ensure requests are only coming from the context you expect Here's a starch recipe.
First of all, if we don't have a sec-fetch header at all, we will just let the request through.
Not all clients will send them.
So we're only enhancing things when they're available.
Next, if you're not expecting any cross site requests, you can just filter to your own site or the user's direct navigations, which is where that 'none' value is coming from.
And finally, a little bit fancy-we're allowing get navigations because we like people linking to us, but we're disallowing those on object and embed types.
Now there was quite a bit from one tip, but web.dev has you covered for docs and you can always just open dev tools and see what's coming in on those requests to your site today.
So on with the countdown and we are halfway there.
At number four it's embeds.
Now in the previous section, we had request headers where the browser was telling us about the context of the request.
And we made a Server Side decision on what to do.
Let's spin that right round and also cover a response header where we can tell the browser what to do.
An embeds in this case is wider than just the embed tech.
We can make use of the Cross-Origin-Resource-Policy to let the browser know what contexts we'd like to allow a resource to be served in.
And as you may have guessed from the previous section, too, we can request, we can restrict that to same-origin or a bit wider with same-site, or we can allow it anywhere with cross-origin.
Now it may be harder to just set blanket defaults for an established site here since cross origin resources are pretty common and not always separated out or known.
However, this is a good opportunity for look, to look for something like an admin section or an internal dashboard where, you know requests only come from one context.
Now we're moving on already though.
But if that little teaser intrigued you, then there is the web.dev security headers guide, which also has a quote section just for you.
But now, it's time to get into the top three.
And this is where we really get into the classics too.
At number three, a long-term member of the scene.
It's pop-ups the default we want to improve here is that when a site calls window.open, even cross site, it ends up with routes to observing and communicating with your site.
Now, again, sometimes you do want this communication, but most of the time there's simply no need.
And we're going to stay with the same head of family as our previous entry Cross-Origin-Opener-Policy.
And that's going to let us specify what communication is going to be allowed.
Or in other words, it's going to let us enforce cross origin isolation for our documents.
Familiar territory then, the first value is same-origin, our strictest set.
However, as soon as you need something like a sign-in service or payments, you may find that you need some of that communication.
So same-origin-allow-popups that value relaxes this a little bit.
You can set this and use window.open yourself, but other sites opening you still get the isolated version.
And finally unsafe-none, which as the name implies, just leaves it all open.
same-origin-allow-popups is a good default for you to set, and it's also what we would like to be able to make the default for the web platform.
As in you get the safer default, and if you need wider cross origin communication, then you actively have to opt in.
There is far more to explore on this topic, like a reporting mode for the policy to enable safe testing and the whole cross origin isolation topic in general.
Luckily I've got that security headers link for you again, and this is basically the greatest hits album for all your security header needs.
Tough competition up at number two, it's another veteran of the scene-iframes.
However, I feel like the controls are a bit more clear-cut here.
The current default is the putting cross origin content in iframe is just allowed.
Again, that's vital-embedded videos, widgets and so on, but there's a lot of scope for abuse here.
So making content available for cross origin iframe use should be a deliberate choice.
And X-Frame-Options lets you control that.
First up, you can just straight deny the ability.
Just say no.
And then there's our usual set.
We will allow a framing, but just from same origin.
So DENY is a good default here.
No iframing of content, unless you've specifically allowed it.
Again, this is also the default we would like to try and provide for the whole platform.
And if you do want to enable iframes for specific content, Content-Security-Policy comes in here with the frame-ancestor's directive.
You can allow iframes for specific URLs or URL patterns, all using the usual CSP, configuration and reporting.
Of course, there is more to investigate here.
So where should you be looking?
That's right.
Web.Dev/security-headers.
Okay.
And here we are.
The number one spot, the tasty treat to be consumed responsibly.
It's cookies.
Now, this is an example where the initial round of changes to improve defaults have been done, but there are still more to come.
So to recap, we added the default value of SameSite=Lax for cookies, meaning they were restricted to same site contexts, often referred to as first party.
And the matching opt-in bit.
If you need cross site third-party cookies, then you also need to specify the SameSite=None and Secure attributes to open that access up, but only in secure contexts.
However, we also want to phase out support for third party cookies entirely, which means there is still going to be work to do on the cookies that have been marked as SameSite=None.
So let's take a peak at a potential future here, and it's a potential future because these are proposals for the web platform.
So they may change based on testing and feedback from developers like you.
The first option is partitioned cookies.
This introduces an opt-in for a cookie to get placed in a separate partitioned cookie jar per top level site.
This means that use cases like embedded widgets that need to save some user preferences or cross-site API calls that might include a client token in a cookie can configure themselves for this one-to-one cross-site communication.
But the important difference is that each of those cookies is kept separate and communication between petitions is not allowed.
I think this is the most applicable option for a lot of you who might provide APIs or iframe content where you rely on cross site cookies for that function, but you don't need the ability to share that same cookie across multiple sites.
Now work is progressing in Chrome, and you can try out the feature behind a flag.
If you want to test it out for your use cases and everything you need to get involved is up here at this chips-proposal link.
The second option is first party sets.
Combined with the SameParty attribute, these are proposals to define that relationship grouping together sites that you own that need access to the same cookie.
Now, this is going to be useful if you operate multiple sites under the same organization, for example, different country-level domains and rely on cookies for functionality that's shared between them.
We also have this available behind a flag in Chrome.
So for details and testing options, there is a first party sets link for you.
And there you have it.
Eight top tips and safer defaults you can put into action.
I hope this has given you some things to look up and implement.
You can find me as @Rowan_M on Twitter, if you want to chat about any of it, but for now, thanks for watching.