For the first few years of the web, developers pretty much coded whatever they thought was cool and shipped it. Specifications, if written at all, were an after thought.
Then, for the next two decades, spec authors drafted increasingly elaborate specifications with optional features and extensibility points meant to be used to enable future work.
Unfortunately, browser and server developers often only implemented enough of the specs to ensure interoperability, and rarely tested that their code worked properly in the face of features and data allowed by the specs but not implemented in the popular clients.
Over the years, the web builders started to notice that specs’ extensibility points were rusting shut– if a new or upgraded client tried to make use of a new feature, or otherwise change what it sent as allowed by the specs, existing servers would fail when they saw the encountered the new values.
In light of this, spec authors came up with a clever idea: clients should send random dummy values allowed by the spec, causing spec-non-compliant servers that failed to properly ignore those values to fail immediately. This concept is called GREASE (with the backronym “Generate Random Extensions And Sustain Extensibility“), and was first implemented for the values sent by the TLS handshake. When connecting to servers, clients would claim to support new ciphersuites and handshake extensions, and intolerant servers would fail. Users would holler, and engineers could follow up with the broken site’s authors and developers about how to fix their code. To avoid “breaking the web” too broadly, GREASE is typically enabled experimentally at first, in Canary and Dev channels. Only after the scope of the breakages is better understood does the change get enabled for most users.
GREASE has proven such a success for TLS handshakes that the idea has started to appear in new places. Last week, the Chromium project turned on GREASE for HTTP2 in Canary/Dev for 50% of users, causing connection failures to many popular sites, including some run by Microsoft. These sites will need to be fixed in order to properly load in the new builds of Chromium.
One interesting consequence of sending GREASE Frames is that it requires moving the END_STREAM flag (recorded as fin=true in the netlog) from the HTTP2_SESSION_SEND_HEADERSframe into an empty (size=0) HTTP2_SESSION_SEND_DATA frame; unfortunately, the intervening GREASE Frame is not presently recorded in the netlog.
You can try H2 GREASE in Chrome Stable using command line flags that enable GREASE settings values and GREASE frames respectively:
Alternatively, you can disable the experiment in Dev/Canary:
GREASE is baked into the new HTTP3 protocol (Cloudflare does it by default) and the new UA Client Hints specification. I expect we’ll see GREASE-like mechanisms appearing in most new web specs where there are optional or extensible features.
Someone complained that a Japanese page is garbled in Edge/Chrome, but renders with the correct characters in Firefox and IE:
The problem is that Chromium is using an unexpected character set to interpret the response in the HTML Parser. That happens because the server doesn’t send a proper character set directive. To avoid problems like this and improve performance, document authors should specify the character set in the HTTP response headers:
The Referrer is omitted in some cases, including:
When the user navigates via some mechanism other than a link in the page (e.g. choosing a bookmark or using the address box)
When navigating from HTTPS pages to HTTP pages
When navigating from a resource served by a protocol other than HTTP(S)
When the page opts-out (details in a moment)
The Referrer mechanism can be very useful, because it helps a site owner understand from where their traffic is originating. For instance, WordPress automatically generates this dashboard which shows me where my blog gets its visitors:
I can see not only which Search Engines send me the most users, but also which specific posts on Reddit are driving traffic my way.
Unfortunately, this default behavior has a significant impact on privacy, because it can potentially leak private and important information.
Imagine, for example, that you’re reviewing a document your mergers and acquisitions department has authored, with the URL https://contoso.com/Q4/PotentialAcquisitionTargetsUpTo5M.docx. Within that document, there might have a link to https://fabrikam.com/financialdisclosures.htm. If you were to click that link, the navigation request to Fabrikam’s server will contain the full URL of the document that led you there, potentially revealing information that your firm would’ve preferred to keep quiet.
Similarly, your search queries might contain something you don’t mind Bing knowing (“Am I required to disclose a disease before signing up for HumongousInsurance.com?”) but that you didn’t want to immediately reveal to the site where you’re looking for answers.
If your web-based email reader puts your email address in the URL, or includes the subject of the current email, links you click in that email might be leaking information you wish to keep private.
The list goes on and on. This class of threat was noted almost thirty years ago:
Websites have always had ways to avoid leaking information to navigation targets, usually involving nonstandard navigation mechanisms (e.g. meta refresh) or by wrapping all links so that they go through an innocuous page (e.g. https://example.net/offsitelink.aspx).
However, these mechanisms were non-standard, cumbersome, and would not control the referrer information sent when downloading resources embedded in pages. To address these limitations, Referrer Policy was developed and implemented by most browsers2.
Referrer Policy allows a website to control what information is sent in Referer headers and exposed to the document.referrer property. As noted in the spec, the policy can be specified in several ways:
Via the Referrer-Policy HTTP response header.
Via a meta element with a name of referrer.
Via a referrerpolicy content attribute on an a, area, img, iframe, or link element.
Via the noreferrer link relation on an a, area, or link element.
no-referrer-when-downgrade – Don’t send the Referer when navigating from HTTPS to HTTP. [The longstanding default behavior of browsers.]
strict-origin-when-cross-origin – For a same-origin navigation, send the URL. For a cross-origin navigation, send only the Origin of the referring page. Send nothing when navigating from HTTPS to HTTP. [Spoiler alert: The new default.]
origin-when-cross-origin For a same-origin navigation, send the URL. For a cross-origin navigation, send only the Origin of the referring page. Send the Referer even when navigating from HTTPS to HTTP.
same-origin – Send the Referer only for same-origin navigations.
origin – Send only the Origin of the referring page.
strict-origin – Send only the Origin of the referring page; send nothing when navigating from HTTPS to HTTP.
As you can see, there are quite a few policies. That’s partly due to the strict- variations which prevent leaking even the origin information on HTTPS->HTTP navigations.
With this background out of the way, the Chromium team has announced that they plan to change the default Referrer Policy from no-referrer-when-downgrade to strict-origin-when-cross-origin. This means that cross-origin navigations will no longer reveal path or query string information, significantly reducing the possibility of unexpected leaks.
I’ve published a few toy test cases for playing with Referrer Policy here.
As noted in their Intent To Implement, the Chrome team are not the first to make changes here. As of Firefox 70 (Oct 2019), the default referrer policy is set to strict-origin-when-cross-origin, but only for requests to known-tracking domains, OR while in Private mode. In Safari ITP, all cross-site HTTP referrers and all cross-site document.referrers are downgraded to origin. Brave forges the Referer (sending the Origin of the target, not the source) when loading 3rd party resources.
Understand the Limits
Note that this new default is “opt-out”– a page can still choose to send unrestricted referral URLs if it chooses. As an author, I selfishly hope that sites like Reddit and Hacker News might do so.
If you’re a web developer, you should test your sites in this new configuration and update them if anything is unexpectedly broken. If you want the browser to behave as it used to, you can use any of the policy-specification mechanisms to request no-referrer-when-downgrade behavior for either an entire page or individual links.
Or, you might pick an even stricter policy (e.g. same-origin) if you want to prevent even the origin information from leaking out on a cross-site basis. You might consider using this on your Intranet, for instance, to help prevent the hostnames of your Intranet servers from being sent out to public Internet sites.
Stay private out there!
1 The misspelling of the HTTP header name is a historical error which was never corrected.
2 Notably, Safari, IE11, and versions of Edge 18 and below only supported an older draft of the Referrer policy spec, with tokens never (matching no-referrer), always (matching unsafe-url), origin (unchanged) and default (matching no-referrer-when-downgrade). Edge 18 supported origin-when-cross-origin, but only for resource subdownloads.
I’ve been working on browsers professionally for 12 of the last 15 years, and in related areas for 20 of the last 20, and over the years I’ve discovered enough surprises in browser behavior that they’re no longer very surprising.
Back in April, I wrote up a quick post explaining how easy it is to delete a single site’s cookies in the new Edge browser. That post was written in response to a compatibility problem with some internal web application that could somehow get in a state where a single “bad” cookie would cause the application to fail to load. The team that owns the application later looked into things further and discovered that the problem was that the application was misbehaving upon receipt of a very old (over a month) session cookie.
Recall that there are two types of cookies:
Persistent cookies, sent to the server until the expiration date supplied when they were set, or until the user clears their cookies, whichever happens first, and
Session cookies, sent to the server until the end of the user’s browser session.
Now, in most cases, developers expect that Persistent cookies will live longer than Session cookies– most users restart their browsers (or computers) every few days, and many modern browsers require restart (to install updates) every few weeks. In contrast, many Persistent cookies are configured to last for a year or more.
So how did this zombie cookie live so long?
Until last week, I didn’t realize that these browser settings in Chrome/Edge76:
…both behave very differently than the old setting from Internet Explorer:
…and the old setting from Edge 18 (Spartan) and earlier:
The Internet Explorer and Edge 18 settings simply open tabs to the URLs of the tabs that were open when you last closed your browser.
In contrast, the Firefox/Chrome/Edge76+ settings restore the browser session itself… which means that closing the browser does not delete your session cookies and doesn’t empty the HTML5 sessionStorage. In many ways, preserving session state makes sense– without it, users are likely to find that their restored tabs are immediately navigated to a login page when the browser is restarted.
However, a consequence of this session restoration behavior is that browsers with this option configured might keep session cookies alive for a very long time:
If you’d like to play with your browser’s behavior, try setting the option and then play with this simple test page. (The background of the page is generated by the session cookie, and the sessionStorage and localStorage values are shown in the text of the page. Adjust the dropdown to change the color.)
Note: If the Chromium-based browser is restarted by visiting chrome://restart or if it restarts to install an update, it behaves as if “Continue where I left off” is set, even if it isn’t.
Web Developers: Given this session resumption behavior, it’s more important than ever to ensure that your site behaves reasonably upon receipt of an outdated session cookie (e.g. redirect the user to the login page instead of showing an error).
Users: If you enable the session resumption option, keep in mind that you can’t simply close your browser to “log out” of a site– you need to explicitly use the site’s logout option (I’ve written about this before).
PS: If you’re really concerned about privacy, you can set the Keep local data only until you quit your browser option:
This will clear all Session and Persistent storage areas every time you exit your browser, regardless of whether you’ve set the “On Startup: Continue where you left off”.
Note: I expect to update this post over time. Last update: 5/7/2020.
As our new Edge Insider builds roll out to the public, we’re starting to triage reports of compatibility issues where Edge79+ (the new Chromium-based Edge, aka Anaheim) behaves differently than the old Edge (Edge18, aka Spartan, aka Edge Legacy) and/or Google Chrome.
In general, Edge79+ will behave very similarly to Chrome. When comparing Edge and Chrome behavior, be sure to compare against the corresponding Chrome Stable, Beta, Dev and Canary channels; Edge 80 vs Chrome 80, etc.
We expect there will be some behavioral deltas between Edge79+ and its Chrome-peer versions, so I’ll note those here too.
In Edge18 and Internet Explorer, attempting to navigate to an App Protocol with no handler installed shows a prompt to visit the Microsoft Store to find a handler. In Chrome/Edge79, the navigation attempt is silently ignored.
Edge 18 and Internet Explorer offer a msLaunchUri API for launching and detecting App Protocols. This API is not available in Edge 79 or Chrome.
Edge 18 and Internet Explorer allow an App Protocol handler to opt-out of warning the user on open using the WarnOnOpen registry key. Edge 79 and Chrome do not support this registry key.
Unlike IE/Edge18, Edge79/Chrome do not support DirectInvoke, a scheme whereby a download is converted into the launch of an application with a URL argument. DirectInvoke is most commonly used when launching Office documents and when running ClickOnce applications. For now, users can workaround the lack of ClickOnce support by installing an extension. Update: In Edge 78+, DirectInvoke is enabled; to enable ClickOnce, see the edge://flags/#edge-click-once setting.
Edge79/Chrome do not support the proprietary msSaveBlob or msSaveOrOpenBlob APIs supported in Edge18. In most cases, you should instead use an A element with a download attribute.
Edge18 did not support navigation to or downloading from data URLs via the download attribute; Edge79/Chrome allow the download of data URLs up to 2mb in length. In most cases, you should prefer blob urls.
Edge 79+/Chrome adopt the system’s proxy settings by default. If a proxy script is supplied, it is evaluated using the built-in V8 script engine. In contrast, Edge 18 and earlier use the WinHTTP Proxy Service in Windows. Further discussion of the implications of this difference can be found at the end of this post.
Microsoft DirectAccess and similar networking software may not work properly when Chromium performs proxy determination internally. You can instruct Edge 79+ to use the WinHTTP Proxy Service by launching the browser with the –winhttp-proxy-resolver command line argument. This feature will be exposed to Group Policy in a future release of Edge.
If you are behind an authenticating proxy server and choose to save your proxy username/password in Edge 18 or Internet Explorer, the WinHTTP Proxy Service will reuse those proxy credentials for subsequent challenges even if you restart the browser. In contrast, if you choose to save your proxy username/password in Edge79+, Chrome, or Firefox, you will be shown an authentication prompt once every time you restart your browser. The username:password will be pre-filled but you must hit “OK” to submit the credentials.
Network Protocols & Cache
Chrome/Edge79+ support the HTTP3/QUIC protocol. Edge 18 and earlier do not.
Edge79 and Chrome send GREASE tokens in HTTPS handshakes; Edge18 does not.
Edge79, Chrome, Firefox, and Safari prohibit connections for HTTP/2 traffic from using banned (weak) ciphers, showing ERR_HTTP2_INADEQUATE_TRANSPORT_SECURITY if the server attempts to use such ciphers. Edge18 did not enforce this requirement. This has primarily impacted intranet websites served by IIS on Windows Server 2012 where the server was either misconfigured or does not have the latest updates installed. Patching the server and/or adjusting its TLS configuration will resolve the problem. End-users should complain to the server operators, and can work around the problem by closing all instances of Edge then restarting with a commandline argument msedge.exe –disable-http2 to disable support for the faster network protocol.
Edge79 and Chrome require certificates that chain to trusted root CAs to be logged in Certificate Transparency (CT). This generally isn’t a problem because public roots are supposed to log in CT as a part of their baseline requirements. However, certain organizations (including Microsoft and CAs) have hybrid roots which are both publicly trusted and issue privately within the organization. As a result, loading pages may error out with NET::ERR_CERTIFICATE_TRANSPARENCY_REQUIRED. To mitigate this, such organizations must either start logging internal certificates in CT, or set one of three policies under HKLM\SOFTWARE\Policies\Microsoft\Edge\. Edge18 does not support CT.
For most (non-EV) certificates, Chrome/Edge79 will not request certificate revocation information from the network (OCSP, CRL download), using revocation information only if it’s cached on the client or stapled in the TLS handshake. Internet Explorer and Legacy Edge would actively hit the network for revocation information by default. See What’s the story with certificate revocation? for discussion.
Edge79 and Chrome use a custom Win32 client certificate picker UI, while Edge18 uses the system’s default certificate picker.
Edge79 and Chrome support the Leave Secure Cookies Alone spec, which blocks HTTP pages from setting cookies with the Secure attribute and restricts the ways in which HTTP pages may interfere with cookies sent to HTTPS pages. Legacy Edge does not have these restrictions.
Edge79 and Chrome support Cookie prefixes (restrictions on cookies whose names begin with the prefixes __Secure- and __Host-). Legacy Edge does not enforce these restrictions.
In Edge79, Edge18, and Firefox, running the browser in InPrivate mode disables automatic Integrated Windows Authentication. Chrome and Internet Explorer do not disable automatic authentication in private mode. You can disable automatic authentication in Chrome by launching it with a command line argument: chrome.exe --auth-server-whitelist="_"
Starting in Edge 82, flags on the edge://flags page allow re-enabling Automatic Authentication for Guest and InPrivate sessions.
Edge18/Edge79 integrates a built-in single-sign-on (SSO) provider, such that configured account credentials are automatically injected into request headers for configured domains; this feature is disabled in InPrivate mode. Chrome does not have this behavior for Microsoft accounts.
Chrome and Edge 79+ choose the strongest HTTP Authentication scheme advertised by the server, regardless of the order of WWW-Authenticate headers provided. In contrast, Edge 18/IE prioritize the first non-BASIC scheme offered. This can lead the new Edge to choose Negotiate in cases where the older Edge might pick NLTM.
By default, Internet Explorer and Edge Legacy would automatically send a client certificate to a server on your Local Intranet if the client only had one certificate available. In Chromium, a Group Policy must be set.
The Edge Platform Status site also includes a short list of features that are supported in Edge18 but not Chromium-derived browsers.
The HTML5 SpeechRecognition API fails silently in Edge 76 to 82 because we do not connect to Google Web Services and have not yet done the work to hook this API up to a Microsoft Web Service. Forum thread.
If you’re trying to use a Chrome command line argument when launching in the new MSEdge.exe and it’s not working, check whether it has “blacklist” or “whitelist” in the name. If so, we probably renamed it.
Chrome and Edge 79+ make very limited use of the Windows Security Zones architecture. See this post for more information.
Browsers identify themselves to servers using a User-Agent header. A top source of compatibility problems is caused by sites that attempt to behave differently based on the User-Agent header and make incorrect assumptions about feature support, or fail to update their checks over time. Please, for the love of the web, avoid User-Agent Detection at all costs!
Chrome User-Agent string:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36
Edge77 Beta (Desktop) User-Agent string: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.19 Safari/537.36 Edg/18.104.22.168
Edge18 User-Agent string: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362
Edge73 Stable (Android) User-Agent string: Mozilla/5.0 (Linux; Android 10; Pixel 3 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.90 Mobile Safari/537.36 EdgA/22.214.171.12492
You’ll note that each of the Edge variants uses a different token at the end of the User-Agent string, but the string otherwise matches Chrome versions of the same build. Sites should almost never do anything with the Edge token information– treat Edge like Chrome. Failing to follow this advice almost always leads to bugs.
Sites are so bad about misusing the User-Agent header that Edge was forced to introduce a service-driven override list, which you can find at edge://compat/useragent. Alas, even that feature can cause problems in unusual cases. For testing, you can tell Edge to ignore the list by starting it thusly:
Note: This blog post was written before the new Chromium-based Microsoft Edge was announced. As a consequence, it mostly discusses the behavior of the Legacy Microsoft Edge browser. The new Chromium-based Edge behaves largely the same way as Google Chrome.
All leading browsers offer a “Private Mode” and they all behave in the same general ways.
While in Private mode, browsers typically ignore any previously cached resources and cookies. Similarly, the Private mode browser does not preserve any cached resources beyond the end of the browser session. These features help prevent a revisited website from trivially identifying a returning user (e.g. if the user’s identity were cached in a cookie or JSON file on the client) and help prevent “traces” that might be seen by a later user of the device.
In Firefox’s and Chrome’s Private modes, a memory-backed cache container is used for the HTTP cache, and its memory is simply freed when the browser session ends. Unfortunately, WinINET never implemented a memory cache, so in Internet Explorer InPrivate sessions, data is cached in a special WinINET cache partition on disk which is “cleaned up” when the InPrivate session ends.
Because this cleanup process may be unreliable, in 2017, Edge Legacy made a change to simply disable the cache while running InPrivate, a design decision with significant impact on the browser’s network utilization and performance. For instance, consider the scenario of loading an image gallery that shows one large picture per page and clicking “Next” ten times:
Another interesting quirk of Edge Legacy’s InPrivate implementation is that the browser will not download FavIcons while InPrivate. Surprisingly (and likely accidentally), the suppression of FavIcon downloads also occurs in any non-InPrivate windows so long as any InPrivate window is open on the system.
Web Platform Storage
Akin to the HTTP caching and cookie behaviors, browsers running in Private mode must restrict access to HTTP storage (e.g. HTML5 localStorage, ServiceWorker/CacheAPI, IndexedDB) to help prevent association/identification of the user and to avoid leaving traces behind locally. In some browsers and scenarios, storage mechanisms are simply set to an “ephemeral partition” while in others the DOM APIs providing access to storage are simply configured to return “Access Denied” errors.
You can explore the behavior of various storage mechanisms by loading this test page in Private mode and comparing to the behavior in non-Private mode.
Within IE and Edge Legacy’s InPrivate mode, localStorage uses an in-memory store that behaves exactly like the sessionStorage feature. This means that InPrivate’s storage is (incorrectly) not shared between tabs, even tabs in the same browser instance.
Beyond the typical Web Storage scenarios, browser’s Private Modes should also undertake efforts to prevent association of users’ Private instance traffic with non-Private instance traffic. Impacted features here include anything that has a component that behaves “like a cookie” including TLS Session Tickets, TLS Resumption, HSTS directives, TCP Fast Open, Token Binding, ChannelID, and the like.
In Private mode, a browser’s AutoComplete features should be set to manual-fill mode to prevent a “NameTag” vulnerability, whereby a site can simply read an auto-filled username field to identify a returning user.
On Windows, most browsers support silent and automatic authentication using the current user’s Windows login credentials and either the NTLM and Kerberos schemes. Typically, browsers are only willing to automatically authenticate to sites on “the Intranet“. Some browsers behave differently when in Private mode, preventing silent authentication and forcing the user to manually enter or confirm an authentication request.
In Firefox Private Mode and Edge Legacy’s InPrivate, the browser will not automatically respond to a HTTP/401 challenge for Negotiate/NTLM credentials.
In Chrome Incognito, Brave Incognito, and IE InPrivate, the browser willautomatically respond to a HTTP/401 challenge for Negotiate/NTLM credentials even in Private mode.
In Edge Legacy and the new Chromium-based Edge, the security manager returns MustPrompt when queried for URLACTION_CREDENTIALS_USE.
Unfortunately Edge Legacy’s Kiosk mode runs InPrivate, meaning you cannot easily use Kiosk mode to implement a display that projects a dashboard or other authenticated data on your Intranet.
For Firefox to support automatic authentication at all, the network.negotiate-auth.allow-non-fqdn and/or network.automatic-ntlm-auth.allow-non-fqdn preferences must be adjusted.
Detection of Privacy Modes
While browsers generally do not try to advertise to websites that they are running inside Private modes, it is relatively easy for a website to feature-detect this mode and behave differently. For instance, some websites like the Boston Globe block visitors in Private Mode (forcing login) because they want to avoid circumvention of their “Non-logged-in users may only view three free articles per month” paywall logic.
Update: Perhaps surprisingly, the new Microsoft Edge sends a deliberate signal to the user’s default search engine when it is loaded in InPrivate mode. The HTTPS request header PreferAnonymous: 1 is sent on requests to the server to allow it to avoid caching any data related to the user’s use of the search engine.
This header is sent only to the search engine, and not to other sites.
Advanced Private Modes
The June 1018 Cumulative Updates increased the per-domain cookie limit from 50 to 180 for IE and Edge across Windows 7, Windows 8.1, and Windows 10 (TH1 to RS2). This higher limit matches Chrome’s cookie jar.
In IE/Edge, if the cookie length exceeds 10240 characters, document.cookie returns an empty string. (Cookies over 1023 characters can also lead to an empty document.cookie string in the event of a race condition). Cookie strings longer than 10KB will still be sent to the server in the Cookie request header, although many servers will reject headers over 16kb in size.
In IE/Edge, the browser will ignore attempts to set (and suppress attempts to send) individual cookies (`name=value`) over 5118 characters in length.