Incognito or private browsing is present in all modern browsers. This mode helps people avoid unwanted cookies, stateful trackers and is also useful in reading articles on newspaper websites since some of them limit the users to a certain number of free articles per day or simply block access if opened in incognito mode.

Some of the things in this post might be similar or different for other families of browsers but I’m only going to focus on Chromium based browsers, more specifically Google Chrome.

Detecting Incognito window before Chrome 74

Before Chrome 74, there was a bug which a lot of websites exploited to detect whether a user is visiting the website in Chrome’s Incognito mode. The websites simply had to attempt to use the FileSystem API, which is used to store temporary or permanent files. This API was disabled in the Incognito mode but was present in the non-incognito mode, thus creating a difference which was exploited to detect if a user was browsing a website using the incognito mode and block these users from viewing the site’s content.

A simple search on Google for Detecting Incognito Window leads to a lot of results, one of them being a Stackoverflow question with the accepted answer being

var fs = window.RequestFileSystem || window.webkitRequestFileSystem;
if (!fs) {
    console.log("check failed?");
} else {
    fs(window.TEMPORARY,
        100,
        console.log.bind(console, "not in incognito mode"),
        console.log.bind(console, "incognito mode"));
}

Google rolled out a new option (accessed by the flag: #enable-filesystem-in-incognito) in Chrome 74, which blocks this detection. Their solution is to create a virtual file system using RAM while in incognito mode. The protection works fine against the above detection method and is going to be enabled by default in the next stable release.

Detecting Incognito window since Chrome 74

It turns out this protection is not enough and it is still possible to detect incognito mode thus making the current protection ineffective. Recently, I was playing around with the Quota Management API and discovered a side-effect to detect incognito mode even with this protection enabled. This API manages the quota assigned for TEMPORARY and PERSISTENT storage available to the applications and websites on the browser. The quota for TEMPORARY storage can be queried by using the following code snippet taken from Jeff Posnick’s article:

if ('storage' in navigator && 'estimate' in navigator.storage) {
  navigator.storage.estimate().then(({usage, quota}) => {
    console.log(`Using ${usage} out of ${quota} bytes.`);
  });
}

There are two kinds of storage available to the websites/applications, TEMPORARY and PERSISTENT, since TEMPORARY storage, as the name suggests is temporary, it can be used without requesting any quota and is shared by all the websites run on the browser.

Some interesting points about TEMPORARY storage and its quota, which I gathered by going through Chromium source code, articles and bug reports: (Ref1,Ref2, Ref3)

  • TEMPORARY storage has a default quota of 50% of the available disk as a shared pool for all the applications/websites
  • Applications/websites can query their quota by calling queryUsageAndQuota() method of the Quota API without any permissions
  • Quota for an incognito window is a fraction (10%) of the device memory with an upper limit of 120MB
  • Quota for a non-incognito window is a fraction of the device storage

The following table lists the minimum TEMPORARY Storage quota available for devices with different disk sizes, which is calculated on the basis of the amount of space the browser attempts to keep free at all times in the device Storage Quota

Based on the above observations, key differences in TEMPORARY storage quota between incognito and non-incognito mode are that in case of incognito mode, there’s a hard limit of 120MB while this is not the case for non-incognito window. And from the above table it’s clear that for the temporary storage quota to be less than 120MB in case of non-incognito mode the device storage has to be less than 2.4GB. However for all practical purposes it is safe to assume that the majority of the devices currently in use have more than 2.4GB of storage.

Using this information, I came up with a simple rule for detecting incognito mode i.e if the temporary storage quota <= 120MB then its safe to say that it’s an incognito window.

if ('storage' in navigator && 'estimate' in navigator.storage) {
	const {usage, quota} = await navigator.storage.estimate();
    console.log(`Using ${usage} out of ${quota} bytes.`);

	if(quota < 120000000){
        console.log('Incognito')
    } else {
        console.log('Not Incognito')
    }	
} else {
	console.log('Can not detect')
}