Cookies under Suborigins

# Aleksandr Dobkin (12 days ago)

Documents are cookie-averse by default in suborigined content (document.cookie returns the empty string and setting document.cookie does nothing). Much of our content makes use of Google Analytics and the current behavior of the analytics script is to silently stop working when cookies are not working. For us at Google, to achieve broad adoption of Suborigins in legacy content, it's sufficient to set the 'unsafe-cookies' flag. This is because, with a few possible exceptions, we do not directly store CSRF tokens in cookies and the values of JS-accessible cookies are not very sensitive.

However, for many sites that store CSRF tokens in cookies, the 'unsafe-cookie' will indeed be unsafe. I would for us to consider alternative designs that would make it easier to adopt Suborigins out of the box, while still being safe for everyone.

Google Analytics (and I suspect similar products) use cookies to store IDs for user and session tracking. Analytics, for example, uses the '_ga' cookie. Nevertheless, to keep existing script working without modification, it is necessary to permit suborigined content to read and write certain cookies.

I suspect the reason for using cookies is mainly historical, and sessionStorage could be used in most cases. However, cookies are occasionally used is for tracking across subdomains of a TLD and across HTTP and HTTPS versions of a site, and here, sessionStorage is not a good replacement.

I briefly looked at the CSRF protection implementation used by Dropbox. Dropbox stores the CSRF token in a cookie called '__Host-js_csrf' (which is httponly) as well as a cookie called 't' (which is normally available via document.cookie). If the suborigned content gets access to the 't' cookie, it would be able to attack other, non-suborigined, content on the domain.

Dropbox will need to change its CSRF mechanism when adapting applications for suborigins, though, and I don't think there is a good way to avoid doing so. It's not safe to reuse the same token among multiple suborigins, so per-suborigin tokens would have to be used. This simplest way to do this would probably be to compute tokens as hmac(value=suborigin, key=site_wide_csrf) and embedding them in HTML.

Considering the three requirements above (compat with legacy tracking scripts, cross-domain tracking, and compat with CSRF cookies), we can come up with different designs and see how well they meet the requirements. I've summarized my findings in the following table. The coumn headings list requirements and various design ideas are listed in the row headings.

                     | compat w/ legacy | cross-domain | safe w/ legacy
                     | tracking scripts | tracking     | CSRF cookies

=========================+==================+==============+================ No cookies | No | No | Yes (current implementation) | | | -------------------------+------------------+--------------+---------------- No cookies restrictions | Yes | Yes | No -------------------------+------------------+--------------+--------------- Prefixed access only | No | No | No -------------------------+------------------+--------------+---------------- Transparent prefixes | Partial | No | Yes -------------------------+------------------+--------------+---------------- Local cookies | Partial | No | Yes -------------------------+------------------+--------------+---------------- Everything but __Host- | Yes | Yes | mostly No -------------------------+------------------+--------------+---------------- Cookie whitelist | Yes | Yes | Yes -------------------------+------------------+--------------+---------------- Cookie blacklist | Yes | Yes | Yes*

  • Yes, but results in cookie inflation and breaks cross-domain tracking. Yes, but breaks cross-domain tracking. * Yes, but requires configuration. ** Yes, but requires and configuration and is dangerous.

Explanation of designs:

No cookies: Document is cookie-averse. Assigning to document.cookie is a no-op. cookie.document is always blank.

No cookie restrictions: Document gets access to document.cookie as though it was not suborigined.

Prefixed access only: Document can only get and set cookies whose names begin with a per-suborigin prefix. Setting document.cookie only works if the cookie names begins with _Sub{name}-. document.cookie only exposes cookies that begin with the same prefix.

Transparent prefixes: Same as above except that prefix are automatically added when assigning to document.cookie. Reading from document.cookie returns unprefxed cookies.

Local cookies: document.cookie string is stored in a hidden LocalStorage field. So assigning to document.cookie works but these cookies are not included in the Cookie header

Everything but Host-: Access to cookies is allowed except for Host- prefixed cookies.

Cookie whitelist: Cookie access is allowed to cookies whose names are whitelisted in the Suborigins header. Setting document.cookie is only allowed if the cookie name is on the whitelist. document.cookie only exposes cookies whose names are on the whitelist.

Cookie blacklist: Same as above, but with a blacklist.

What does everyone think of these solutions? Are we open to potentially adopting any of them?

From the list above, 'local cookies' and 'cookie whitelist' designs meet

most of the requirements. Personally, I think the 'local cookies' solution would be a useful to have and could be safely enabled by default. The 'cookie whitelist' feature might be useful as well, but is more dangerous and also requires a whitelist be specified.

Contact us to advertise here
# Jochen Eisinger (11 days ago)

On Tue, May 9, 2017 at 5:39 AM Aleksandr Dobkin dobkin@google.com wrote:

Documents are cookie-averse by default in suborigined content (document.cookie returns the empty string and setting document.cookie does nothing). Much of our content makes use of Google Analytics and the current behavior of the analytics script is to silently stop working when cookies are not working. For us at Google, to achieve broad adoption of Suborigins in legacy content, it's sufficient to set the 'unsafe-cookies' flag. This is because, with a few possible exceptions, we do not directly store CSRF tokens in cookies and the values of JS-accessible cookies are not very sensitive.

However, for many sites that store CSRF tokens in cookies, the 'unsafe-cookie' will indeed be unsafe. I would for us to consider alternative designs that would make it easier to adopt Suborigins out of the box, while still being safe for everyone.

Google Analytics (and I suspect similar products) use cookies to store IDs for user and session tracking. Analytics, for example, uses the '_ga' cookie. Nevertheless, to keep existing script working without modification, it is necessary to permit suborigined content to read and write certain cookies.

I suspect the reason for using cookies is mainly historical, and sessionStorage could be used in most cases. However, cookies are occasionally used is for tracking across subdomains of a TLD and across HTTP and HTTPS versions of a site, and here, sessionStorage is not a good replacement.

I briefly looked at the CSRF protection implementation used by Dropbox. Dropbox stores the CSRF token in a cookie called '__Host-js_csrf' (which is httponly) as well as a cookie called 't' (which is normally available via document.cookie). If the suborigned content gets access to the 't' cookie, it would be able to attack other, non-suborigined, content on the domain.

Dropbox will need to change its CSRF mechanism when adapting applications for suborigins, though, and I don't think there is a good way to avoid doing so. It's not safe to reuse the same token among multiple suborigins, so per-suborigin tokens would have to be used. This simplest way to do this would probably be to compute tokens as hmac(value=suborigin, key=site_wide_csrf) and embedding them in HTML.

Considering the three requirements above (compat with legacy tracking scripts, cross-domain tracking, and compat with CSRF cookies), we can come up with different designs and see how well they meet the requirements. I've summarized my findings in the following table. The coumn headings list requirements and various design ideas are listed in the row headings.

                     | compat w/ legacy | cross-domain | safe w/ legacy
                     | tracking scripts | tracking     | CSRF cookies

=========================+==================+==============+================ No cookies | No | No | Yes (current implementation) | | |

-------------------------+------------------+--------------+---------------- No cookies restrictions | Yes | Yes | No -------------------------+------------------+--------------+--------------- Prefixed access only | No | No | No

-------------------------+------------------+--------------+---------------- Transparent prefixes | Partial* | No | Yes

-------------------------+------------------+--------------+---------------- Local cookies | Partial** | No | Yes

-------------------------+------------------+--------------+---------------- Everything but __Host- | Yes | Yes | mostly No

-------------------------+------------------+--------------+---------------- Cookie whitelist | Yes | Yes | Yes

-------------------------+------------------+--------------+---------------- Cookie blacklist | Yes | Yes | Yes**

  • Yes, but results in cookie inflation and breaks cross-domain tracking. Yes, but breaks cross-domain tracking. * Yes, but requires configuration. ** Yes, but requires and configuration and is dangerous.

Explanation of designs:

No cookies: Document is cookie-averse. Assigning to document.cookie is a no-op. cookie.document is always blank.

No cookie restrictions: Document gets access to document.cookie as though it was not suborigined.

Prefixed access only: Document can only get and set cookies whose names begin with a per-suborigin prefix. Setting document.cookie only works if the cookie names begins with _Sub{name}-. document.cookie only exposes cookies that begin with the same prefix.

Transparent prefixes: Same as above except that prefix are automatically added when assigning to document.cookie. Reading from document.cookie returns unprefxed cookies.

Local cookies: document.cookie string is stored in a hidden LocalStorage field. So assigning to document.cookie works but these cookies are not included in the Cookie header

Everything but Host-: Access to cookies is allowed except for Host- prefixed cookies.

Cookie whitelist: Cookie access is allowed to cookies whose names are whitelisted in the Suborigins header. Setting document.cookie is only allowed if the cookie name is on the whitelist. document.cookie only exposes cookies whose names are on the whitelist.

Cookie blacklist: Same as above, but with a blacklist.

What does everyone think of these solutions? Are we open to potentially adopting any of them?

From the list above, 'local cookies' and 'cookie whitelist' designs meet most of the requirements. Personally, I think the 'local cookies' solution would be a useful to have and could be safely enabled by default. The 'cookie whitelist' feature might be useful as well, but is more dangerous and also requires a whitelist be specified.

It looks like the "local cookies' could also be implemented as a small polyfill, right?

"Regular cookies" and "no cookies" are easy to spec, while the new models you describe would require defining a new thing, so I'd lean towards punting this to after the v1 has launched, wdyt?

# Devdatta Akhawe (10 days ago)

Hey Alex

I agree with Jochen. We should punt on this for v1 in the interest of getting something useful, not perfect, out.

I am curious: why doesn't a polyfill that postMessages to an iframe that is on the physical origin work for you? This also allows you to implement policies of arbitrary complexity. In the spirit of extensible web, I think this is far more practical than the spec trying to mandate what's right and what's wrong: we will screw this up. The web is very complex and diverse.

# Aleksandr Dobkin (10 days ago)

We want to roll out suborigins on our static content pages en mass, e.g. all the pages on www.google.com/about and polyfills would be a lot of work. The right way to do it would be to update the templates that are used to generate the HTML pages. There several challenges:

1) Sometimes it is hard to find the template. If you know you have page X, finding template Y for it is nontrivial. We have thousands of individual pages/templates. 2) Sometimes it is hard to export the template to HTML correctly because the config files are in a bad state, not present, bitrotted, etc. The templates the template depends on are updated from time to time, and exporting can result in unexpected changes in output, so updates frequently require manual QA.

We could insert the polyfills in the HTML files directly, but then the polyfill would be be lost the next time the templates are exported, and the error (broken analytics) might not be noticed right away. We could build some automation to fix this, though.

It's a lot easier to deploy if it's just a flag in a header.

I'm okay to punt on this until after v1. For us, the 'unsafe-cookie' flag seems sufficient and safe. I think it would be worth it if we can spec something so that suborigins works better out of the box with commonly-used JS.

On Wed, May 10, 2017 at 8:38 AM, Devdatta Akhawe dev.akhawe@gmail.com

wrote:

# Devdatta Akhawe (10 days ago)

Aah ok. Is it not possible to add a simple "add these 3 lines of HTML to the top?" at the front end layer (whatever google has for nginx) or via a serviceworker?

In any case, I don't want to backseat pontificate about the problems you are hitting. I agree that we should spec suborigins so that it works better out of the box with commonly used JS. I am still not convinced this particular concern--"cannot add shims easily" -- is a common problem but I might be naive. Lets see what feedback we get on v1.

Want more features?

Request early access to our private beta of readable email premium.