138

Is it possible to determine if Google Chrome is in incognito mode via a script?

Edit: I actually meant is it possible via user-script, but the answers assume JavaScript is running on a web page. I've re-asked the question here in regards to user scripts.

Community
  • 1
  • 1
RodeoClown
  • 13,338
  • 13
  • 52
  • 56
  • And you have to remember that the user must allow incognito mode for the extension manual. By default, everything is true. – Mohamed Mansour May 26 '10 at 01:23
  • @Mohamed: The user having to allow it would be me, so that won't be a problem :) – RodeoClown May 26 '10 at 20:01
  • 1
    Wait, I just realised I didn't make myself clear - I meant with a user script. I'll keep this question alive, as it has some useful answers (thanks), but I'll ask another question with extra clarification. – RodeoClown May 26 '10 at 21:17
  • 1
    http://stackoverflow.com/questions/2860879/detecting-if-a-browser-is-using-private-browsing-mode – Josh Lee May 29 '13 at 01:27
  • @aren Why not make it easy for script to determine if being viewed in incognito/private mode? The page is still executed in the browser sandbox and (theoretically) can't do anything to circumvent the privateness of the page visit, yet the programmer could add value by knowing it's incognito. Am I missing something? – Pete Alvin Apr 18 '16 at 13:44
  • 1
    @PeteAlvin Or the site could detract value by detecting incognito. Boston Globe's site won't display its articles in incognito, preventing the user from circumventing their free articles quota. In general, I prefer a site know less about me. – JohnC Jun 17 '17 at 17:08
  • Exists a lib to detect browsing mode: https://github.com/Maykonn/js-detect-incognito-private-browsing-paywall/ – Maykonn Aug 02 '18 at 20:12
  • Yes, but should you? (Hint: no you shouldn't) – Anonymous Apr 10 '19 at 16:54
  • Unless you want to, in which case it's fine. – RodeoClown Apr 10 '19 at 20:35
  • 1
    "Chrome will likewise work to remedy any other **current or future** means of Incognito Mode detection." [quote from Google blog](https://www.blog.google/outreach-initiatives/google-news-initiative/protecting-private-browsing-chrome/) - so any solution is likely to stop working, at some point in the future. – sudoqux Jul 28 '20 at 06:38
  • @TiagoRangeldeSousa I own the repo detectIncognito mentioned below. Have you tested that, and if so did it not work? Presently I am able to detect incognito on all modern browsers. – Joe Rutkowski Apr 13 '22 at 21:37

11 Answers11

253

The functionality of this answer is Chrome version dependant. The most recent comment was this works in v90

Yes. The FileSystem API is disabled in incognito mode. Check out https://jsfiddle.net/w49x9f1a/ when you are and aren't in incognito mode.

Sample code:

    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"));
    }
Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
Alok
  • 3,127
  • 2
  • 16
  • 18
  • This is brilliant! Thank you soo much! The only problem is that it's asynchronous and since I have a situation where I have to wait for that check it slows down the page load by a few miliseconds. – user2718671 Feb 24 '17 at 13:38
  • @user2718671 do the check once and then set a cookie. A few ms on the first page load should not be noticeable in the larger scheme of things. – Alok Feb 28 '17 at 01:29
  • @Alok Thanks for that information. But I think in my case it wouldn't help because the user can't use all functionalities on the page if incognito mode is enabled. After the check a notification about that is displayed that the user if he wants to use all functionalities he should leave incognito mode and refresh the page. So the check every time he enters the page makes sense. – user2718671 Feb 28 '17 at 10:53
  • @user2718671 Again, if a few ms on a single page is a concern, you are doing something wrong. The cost of establishing a connection to a website (DNS + TCP + TLS) should dwarf your ms hit on the filesystem API call. – Alok Mar 01 '17 at 20:49
  • @Alok: Well, in the end the detection didn't work in all browsers anyway. Please check out: http://stackoverflow.com/questions/2860879/detecting-if-a-browser-is-using-private-browsing-mode#22623958 What I did was to check if local storage and cookies are available and made the site work with some more if and else conditions. That worked best for me and the users. – user2718671 Mar 09 '17 at 15:39
  • 2
    @user2718671 http://jsfiddle.net/w49x9f1a/ still works fine for me in latest Chrome on mac osx. weird... – Alok Mar 09 '17 at 22:04
  • @Alok: I think it fails for Safari's Incognito Mode on Iphone 7. Safari - the new Internet Explorer ;) – user2718671 Mar 10 '17 at 08:19
  • 1
    this is not working anymore (maybe it worked in the past) – Alexandru R May 02 '17 at 08:35
  • @AlexandruRada still works for me. Again, this is for Chrome on the desktop. It's not going to work with other browsers/envs. – Alok May 02 '17 at 21:34
  • 1
    I tried at Chrome. It works, but the logic is backwards. – Lucas Massuh Aug 16 '17 at 13:19
  • I had the same experience as Lucas, it seems to work if you reverse the `console.log.bind` statements (I used an anonymous function there to set a variable to true or false) – Jay Aug 29 '17 at 18:31
  • Thinking this is out of date. When I log out the fs instance, I get an instance of ```webkitRequestFileSystem``` in both contexts. – mccambridge Dec 18 '17 at 21:20
  • Exists a lib to detect browsing mode: https://github.com/Maykonn/js-detect-incognito-private-browsing-paywall/ – Maykonn Aug 02 '18 at 20:13
  • Does this fail any errors with older browsers or other browsers other than Chrome/Firefox? I want to be sure it doesnt cause errors in like IE etc – tmarois Nov 07 '18 at 15:42
  • This is for Chrome ONLY!! so.. do not use it for OTHER browsers – user2434435 Dec 17 '18 at 13:08
  • 11
    This is going to go away soon thanks to [these](https://chromium-review.googlesource.com/q/bug:93417) commits – blueren Feb 16 '19 at 06:35
  • Semi-related to the question, `navigator.serviceWorker` is undefined in Firefox' incognito mode. Might be worth poking at Chrome's serviceWorker API to see if any differences pop up. – Alok Mar 20 '19 at 03:29
  • Funny enough @Alok - last month Chrome tried to beat your script, [but failed](https://superuser.com/questions/1432880)! – LWC May 04 '19 at 10:07
  • Try running Chrome's incognito mode with `disable-web-security` flag and the FileSystem API will still work on it – Muhammad Faizan Uddin Jun 07 '19 at 22:46
  • 1
    Meanwhile, Chrome v75 came out and beat the script (if you run JSFiddle it'll look not private when it is). But it only beats the script if the user enables [the aforementioned secret opt-it setting](https://superuser.com/questions/1432880). I assume by v76 this setting will be enabled by default and render this script obsolete. – LWC Jun 11 '19 at 20:46
  • 1
    Chrome v76 came up and just beats the script by default, which means this answer is no longer valid. But I read somewhere there's a limit on how much file size Chrome gives access to in InCogntio mode. So you may try to adjust the script to use this in order to beat Chrome again. – LWC Aug 01 '19 at 05:26
  • 1
    Correct, see https://mishravikas.com/articles/2019-07/bypassing-anti-incognito-detection-google-chrome.html. – Alok Aug 12 '19 at 20:38
  • 13
    fail in chrome v 77.0.3865.90 – Gitesh Purbia Sep 26 '19 at 11:21
  • 1
    const {usage, quota} = await navigator.storage.estimate(); if(quota < 120000000){//Incognito} else {// not Incognito} This thing is still working in latest chrome browser. – Amit Aug 07 '20 at 09:45
  • This solution is no longer valid in newer chrome versions. I verified in Version 84.0.4147.135 (Official Build) (64-bit).And this is no longer the case. – Pragati Dugar Aug 27 '20 at 12:36
  • try https://stackoverflow.com/a/57438917/2217509 for more recent Chrome versions. – Alok Aug 28 '20 at 16:07
  • It seems to be ok for my machine which uses Chrome Version 87.0.4280.67 (Official Build) (x86_64), it evens work when I changed the size to `0`. `fs(window.TEMPORARY, 0, ...` – schrodinger's code Nov 25 '20 at 09:25
  • This fails on Chrome 106 (Ubuntu 20.04) – Ruben Alves Oct 12 '22 at 13:16
  • This answer is no longer correct. The ability to detect Incognito was removed in [Chrome 80 (File System API disabled)](https://bugs.chromium.org/p/chromium/issues/detail?id=990592) and [81 (File System quota limit)](https://bugs.chromium.org/p/chromium/issues/detail?id=1017120). See https://www.blog.google/outreach-initiatives/google-news-initiative/protecting-private-browsing-chrome/ – JDB Oct 25 '22 at 18:31
24

In Chrome 74 to 84.0.4147.135 you can determine this by estimating the available file system storage space

See the jsfiddle

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')
}
Vinnie James
  • 5,763
  • 6
  • 43
  • 52
  • 1
    Unfortunately, this doesn't work now: for example, in newest MS's EDGE I have 224053418 bytes in so called Private mode. In Chrome I see 234549212 bytes in Incognito mode. – djdance Dec 21 '20 at 06:56
20

One way is to visit a unique URL and then check to see whether a link to that URL is treated as visited by CSS.

You can see an example of this in "Detecting Incognito" (Dead link).

Research paper by same author to replace Detecting Incognito link above

In main.html add an iframe,

 <iframe id='testFrame' name='testFrame' onload='setUniqueSource(this)' src='' style="width:0; height:0; visibility:hidden;"></iframe>

, and some JavaScript code:

function checkResult() {
  var a = frames[0].document.getElementById('test');
  if (!a) return;

  var color;
  if (a.currentStyle) {
    color = a.currentStyle.color;
  } else {
    color = frames[0].getComputedStyle(a, '').color;
  }

  var visited = (color == 'rgb(51, 102, 160)' || color == '#3366a0');
  alert('mode is ' + (visited ? 'NOT Private' : 'Private'));
}

function setUniqueSource(frame) {
  frame.src = "test.html?" + Math.random();
  frame.onload = '';
}

Then in test.html that are loaded into the iFrame:

<style> 
   a:link { color: #336699; }
   a:visited { color: #3366A0; }
</style> 
<script> 
  setTimeout(function() {
    var a = document.createElement('a');
    a.href = location;
    a.id = 'test';
    document.body.appendChild(a);
    parent.checkResult();
  }, 100);
</script> 

NOTE: trying this from the filesystem can make Chrome cry about "Unsafe Javascript". It will, however, work serving from a webserver.

Rob
  • 14,746
  • 28
  • 47
  • 65
JHurrah
  • 1,988
  • 16
  • 12
  • That's pretty cool, i didn't realise that incognito mode doesn't highlight visited links. This requires user to click a link though. – Igor Zevaka May 26 '10 at 00:29
  • 1
    No it doesn't, the iframe "clicks" the link. – kibibu May 26 '10 at 03:31
  • 42
    This doesn't actually work since most browsers don't expose :visited style information through javascript. The CSS interface will say as if the link has the non-visited color. This is a security measure that has been in WebKit- and Gecko browsers at least since 2010. This was to protect the user's history (e.g. one could try all possible URLs, and send the visited ones to a third party. This way one could get access to tokens in urls and what not). – Timo Tijhof Oct 11 '12 at 02:52
  • 3
    Official Mozilla article explaining the privacy changes regarding `:visited`: https://hacks.mozilla.org/2010/03/privacy-related-changes-coming-to-css-vistited/ – Denilson Sá Maia Feb 29 '16 at 18:04
7

You can, in JavaScript, see JHurrah's answer. Except for not highlighting links, all incognito mode does is not save browse history and cookies. From google help page:

  • Webpages that you open and files downloaded while you are incognito aren't recorded in your browsing and download histories.
  • All new cookies are deleted after you close all incognito windows that you've opened.

As you can see the differences between normal browsing and incognito happen after you visit the webpage, hence there is nothing that browser communicates to the server when it's in this mode.

You can see what exactly your browser sends to the server using one of many HTTP request analysers, like this one here. Compare the headers between normal session and incognito and you will see no difference.

Community
  • 1
  • 1
Igor Zevaka
  • 74,528
  • 26
  • 112
  • 128
  • Recently, it disables all extensions except those extensions which were specifically marked by the user as incognito safe. – oxygen Oct 12 '12 at 17:39
5

If you are developing an Extension then you can use the tabs API to determine if a window/tab incognito.

More information can be found here.

If you are just working with a webpage, it is not easy, and it is designed to be that way. However, I have noticed that all attempts to open a database (window.database) fail when in incongnito, this is because when in incognito no trace of data is allowed to be left on the users machine.

I haven't tested it but I suspect all calls to localStorage fail too.

Edric
  • 24,639
  • 13
  • 81
  • 91
Kinlan
  • 16,315
  • 5
  • 56
  • 88
5

For those looking for a solution, here's a brief rundown of the current methods of detecting Private Browsing modes in various browsers as of October 2021:

  • Chromium: Similar to Vinnie James's answer, call navigator.storage.estimate(), grab the quota property and compare it to performance.memory.jsHeapSizeLimit. If the quota property is less than jsHeapSizeLimit, it's incognito. If jsHeapSizeLimit is undefined, use 1073741824 (1 GiB).

  • Safari for macOS: Use safari.pushNotification.requestPermission on a non-existent push server & grab the error. If "gesture" does not appear in the error, it's in private mode.

  • Safari for iOS: Create an iframe & add an error event listener using contentWindow.applicationCache on the iframe. If the error trips, it's in private mode.

  • Firefox: navigator.serviceWorker will be undefined in a private window.

  • Internet Explorer: window.indexedDB will be undefined in InPrivate mode.

You can see an implementation of these methods in the detectIncognito script I have available on GitHub.

Joe Rutkowski
  • 171
  • 1
  • 3
  • 6
2

Update This seems to not be working anymore


This uses a promise to wait for the asynchronous code to set a flag, so we can use it synchronously afterward.

let isIncognito = await new Promise((resolve, reject)=>{
    var fs = window.RequestFileSystem || window.webkitRequestFileSystem;
    if (!fs) reject('Check incognito failed');
    else fs(window.TEMPORARY, 100, ()=>resolve(false), ()=>resolve(true));      
});

then we can do

if(isIncognito) alert('in incognito');
else alert('not in incognito');

Note, to use await you need to be inside an async function. If you're not, you can wrap all your code inside one to be able to

aljgom
  • 7,879
  • 3
  • 33
  • 28
  • This looks very interesting and is a form of Javascript that I'm unfamiliar with and seems unsupported by my Netbeans 8.2 IDE in Windows 10. But if I can get it to work, I'm also curious: could I replace `reject('Check incognito failed')` with `resolve(false)` if I want `isIncognito` to be a boolean that is only ever true or false? Thanks. – Ryan Apr 03 '18 at 01:43
  • 3
    I see `Syntax Error: await is a reserved word`, and I think `await` always needs to be within an `async` function. So I think this code doesn't work. – Ryan Apr 03 '18 at 01:50
  • 1
    `reject` would raise an exception if it is called, and a value would not be returned, so `isIncognito` would always be `true` or `false` the way the code is written. However, you would also be able to use a 'resolve' there if you want a value returned. And it might depend on how the environment handles await in the global scope? It works on the chrome console in the global scope, but you could try wrapping everything in `(async function() { 'everything here' })();` so it is run inside an async function – aljgom Apr 04 '18 at 04:18
  • 3
    Alternatively, you could just save the promise to `isIncognito` instead of the result, and run it afterwards in an async function: `let isIncognito = new Promise( ...etc` then somewhere else you'd have a function like `(async function() { if( !(await isIncognito) ) {alert('not incognito') } })();` – aljgom Apr 04 '18 at 04:21
  • 2
    This answer is invalidated in chrome 76+ – Cruncher Aug 12 '19 at 15:16
  • Do not work for me. – Pioz Mar 01 '23 at 15:09
0

Quick function based on Alok's Answer (note: this is asynchronous)

Update - not working anymore

function ifIncognito(incog,func){
    var fs = window.RequestFileSystem || window.webkitRequestFileSystem;
    if (!fs)  console.log("checking incognito failed");
    else {
        if(incog) fs(window.TEMPORARY, 100, ()=>{}, func);
        else      fs(window.TEMPORARY, 100, func, ()=>{});
    }
} 

usage:

ifIncognito(true,  ()=>{ alert('in incognito') });
// or
ifIncognito(false, ()=>{ alert('not in incognito') });
aljgom
  • 7,879
  • 3
  • 33
  • 28
0

Here is the suggested answer written in ES6 syntaxt and slightly cleand up.

const isIncognito = () => new Promise((resolve, reject) => {
    const fs = window.RequestFileSystem || window.webkitRequestFileSystem;
    
    if (!fs) {
        reject('Cant determine whether browser is running in incognito mode!');
    }

    fs(window.TEMPORARY, 100, resolve.bind(null, false), resolve.bind(null, true));
});

// Usage
isIncognito()
    .then(console.log)
    .catch(console.error)
Zoe
  • 27,060
  • 21
  • 118
  • 148
nikksan
  • 3,341
  • 3
  • 22
  • 27
0

Other answers seem to be no longer valid in recent chrome versions.

The idea is to find out storage estimates to determine if the tab is incognito or not. Storage size is less for incognito tabs.

Run this code in both normal and incognito window and note down the quota size.

const {quota} = await navigator.storage.estimate();
console.log(quota);

use quota size difference to implement the logic for incognito mode detection.


below logic works for Chrome v105:

const { quota } = await navigator.storage.estimate();

if (quota.toString().length === 10) {
  console.log("gotcha: this is incognito tab"); //quota = 1102885027
} else {
  console.log("this is a normal tab"); //quota = 296630877388
}

Also, look at this solution for much wider support (includes other browsers as well) detectIncognito.ts

demo: https://detectincognito.com/

GorvGoyl
  • 42,508
  • 29
  • 229
  • 225
  • Not sure how this is working for you in v105. In `Version 106.0.5249.103 (Official Build) (x86_64)`, an incognito tab is showing "this is a normal tab". Detecting incognito mode via quota was fixed in 2020: https://bugs.chromium.org/p/chromium/issues/detail?id=1017120 – JDB Oct 25 '22 at 18:36
-4

This works in May 2021: https://jsfiddle.net/2b1dk8oa/

The script has to be executed in a webpage, which is in an iframe.

try{
    var ls = localStorage;
    alert("You are not in Incognito Mode.");
}
catch(e) {  alert("You are in Incognito Mode.");  }
Zoe
  • 27,060
  • 21
  • 118
  • 148
Ivan Kuckir
  • 2,327
  • 3
  • 27
  • 46