When document.domain is not equal to document.domain


One of our most popular Canarytokens is one we call the "Cloned-Site Token". Essentially, we give you a tiny piece of JavaScript to add to your public webpage. If this JS is ever loaded on a server that doesn't belong to you, it fires an alert. You can be alerted at an email address or webhook in the free version, or to your SIEM, slack channel or a bunch of other alternatives in the paid version.

The Cloned-Site Token is super useful at catching Phishers who duplicate your website as a pre-cursor to an actual phishing attack.

A notification that the website from http://thinkst.com was now running on http://fake-thinkst.com

The Issue

Recently, a financial services customer was periodically getting alerts where the Cloned-Site domain matched their actual domain. This was unexpected, as the token explicitly should only trigger if the domains are different. In other words, the token for http://domain.com should only fire if the page is loaded at a different URL, but in this case the alert was firing even though the page was (supposedly) loaded at the legitimate URL http://domain.com.

First thing to do was to investigate the alert:

Date: Thu Jun 20 2019 08:36:12 GMT+0000 (UTC)
Original Site: xxxxxxxx.com
Cloned Site: https://xxxxxxxx.com
Headers:      Accept: image/webp,image/apng,image/*,*/*;q=0.8
     Accept-Encoding: gzip, deflate
     Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
     Connection: keep-alive
     Forwarded: for=
     Save-Data: on
     Scheme: http
     User-Agent: Mozilla/5.0 (Linux; Android 7.0; VTR-L09) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.89 Mobile Safari/537.36
     Via: 1.1 Chrome-Compression-Proxy

Okay… There are a few interesting things here. Firstly, all the alerts seemed to be coming from mobile devices and more specifically via the “Chrome-Compression-Proxy”. What the heck is that thing? After a bit of Googling it turns out that if you enable the Data Saver feature on your android device it routes all traffic through a Google proxy.

I’m glad you asked. According to peeps at Google, “Data Saver may reduce data use by up to 90% and load pages two times faster, and by making pages load faster, a larger fraction of pages actually finish loading on slow networks.”

Google optimises the page being fetched by performing a bunch of built-in optimisations and using Google servers. Some of the optimisations include:
  • Rewriting slow pages
  • Image replacement in the form of placeholders
  • Disable scripts
  • Stop loading of non-critical resources
Google calls these optimisations Chrome Lite pages (https://blog.chromium.org/2019/03/chrome-lite-pages-for-faster-leaner.html). You can imagine how they do this for HTTP pages, but they recently announced Lite support for HTTPS pages too.

Digging into it

At this point I turned to my mobile device to try and recreate the cloned site alert and after a bit of fiddling I managed to trigger it BUT ONLY ONCE!

For our customer we deployed a small server-side fix to make the token work again but were curious about these Chrome Lite pages. If Google is rewriting my site’s HTML, how are they not breaking SSL? Are they mitm’ing my site?

We hosted a toy site with a bunch of static files and viewed the access logs on page load to see what files came from Google and which were requested from our server.

It turns out that triggering Lite mode for a site is annoyingly difficult. Some things I tried was:
  • Creating an enormously large index page. This should be rewritten right? Maybe?
  • Massive unminified JS files
  • Massive unminified CSS files
  • Including links to content that was being blocked by Lite mode on other sites
One method that did turn out to work consistently was to kill Chrome on my mobile device while on the target page and then reopen Chrome.  (ick!)

Chrome has a nifty feature that allows you to remotely debug your mobile phone’s browser (good luck opening dev tools on your phone). Having the ability to see what network operations were taking place in the browser was great. I could see what items Lite mode would reject and which items it would minify.

Unfortunately, I was still having trouble recreating the Cloned-Site alert we'd seen on the customer's page (I had only ever triggered it once). It took me a few days to realise that the fix I implemented on our backend was blocking me... 

Even with that taken into account, I was still unable to reproduce the triggered alert. (At this point I had spent way too much time trying to trigger the alert / force Lite mode, without any wins).

Then, almost while putting it to bed, we had a happy accident. Without thinking I hit cmd + R to refresh the mobile browser on my desktop via the debugging window and hey! the Canarytoken triggered!

It seems like there’s a flow in Chrome (Lite mode via close/open) that sets 'document.domain' to the empty string "", which is the reason the alert was triggering. (The observant reader would note that our token reported it was running at http://domain.com, that's because we checked document.domain, and reported location.href. The bug means a disconnect between the two.)

So, if you were using Chrome, and your connectivity was bad enough, you'd drop to Lite pages mode, and then it would be possible for the document to be served from ... Chrome on reload? So the document.domain would suddenly be "".


This seems to be pretty unexpected behaviour and is interesting to us for two reasons:

    1. Any site making use of document.domain will have a bad day;
    2. We wouldn't have known any of this was happening without a well-deployed Canarytoken!

This is the second time that Canarytokens deployed by users have found Chrome flirting with the creepy line. In 2018, Kelly Shortridge found Chrome reading files in her Documents folder:

It's the value of both Canaries and Canarytokens. Knowing when things go bump in the night.


We pinged the Chrome team and got this reply:
Max, some of the lite pages are served locally by Chrome itself.  Specifically, if Chrome has an offline version of the page available locally on the device, it will serve that page directly from cache. Since that page is not coming directly from origin, document.origin for those pages is not set.


  1. When I saw the title of this article, I thought that it is unreal until I have checked it. As I am a programmer, I was so impressed about this ''domain'' error. Thanks for the explanation.


Post a Comment

Check out some of our other popular posts:

We bootstrapped to $11 million in ARR

On SolarWinds, Supply Chains and Enterprise Networks

New features aren't Solved Problems