I'm building an extranet for a company paranoid about security. They want to make sure that (among other things) their users are browsing the site with the Private Browsing mode switched on in their web browser so that no cookies or history is kept.
I found only this
http://jeremiahgrossman.blogspot.com/2009/03/detecting-private-browsing-mode.html
and
https://serverfault.com/questions/18966/force-safari-to-operate-in-private-mode-and-detect-that-state-from-a-webserver
The ideal solution would use no or minimal javascript. Would attempting to set a unique cookie work for all browsers and platforms? Anyone done this before?
thanks!
update
http://crypto.stanford.edu/~collinj/research/incognito/ uses the CSS visited technique of the browser fingerprinters mentioned by other posters- thanks for the hints.
I like it because it is small and elegant, but still want to be able to do it without javascript if possible.
Update June 2019
Google is removing the ability to detect Private Browsing Mode permanently in Chrome 76 onwards. So, if you're wanting to detect private browsing it's now impossible (unless you find a way to do it that Google hasn't found). The ability to detect private browsing mode has been acknowledged as a bug and was never intended.
To anyone else coming across this question, please note as of 2014, there is no reliable or accurate way to detect if someone is browsing in an incognito/private/safe browsing mode through Javascript or CSS. Previous solutions that once worked like the CSS history hack have since been rendered unusable by all browser vendors.
There should never be a situation where needing to detect private browsing mode on a normal day-to-day website is ever needed. People are choosing to browse anonymously and or not anonymously for their own reasons.
Browsers like Chrome and Firefox do not disable functionality like localStorage any more. They simply namespace it in a temporary location to prevent websites that use it from erroring out. Once you're finished browsing, the namespace is erased and nothing is saved. If you are testing for localStorage support regardless of mode, it will always return true for browsers that support it.
Other means of detecting private mode in Chrome specifically have been completely patched and will no longer work.
If it is required internally by a company, you should develop a browser plugin. Chrome and Firefox, in particular, expose internal API's which allow plugins to check if the user is in private browsing/incognito mode and action accordingly. It cannot be done outside of a plugin.
Here's an easier way to do detect privacy mode. This works in Safari only. I created it because a web app I am developing uses localStorage. LocalStorage is not available in Safari when in privacy mode, thus my app will not work.
On page load, run the script below. It shows an alert box if we cannot use localStorage.
try {
// try to use localStorage
localStorage.test = 2;
} catch (e) {
// there was an error so...
alert('You are in Privacy Mode\nPlease deactivate Privacy Mode and then reload the page.');
}
Current state
Google Chrome has developed further and leaves no more space for detection when using incognito mode. Same might apply for other browsers.
Old solutions (might partially work)
It is possible to detect enabled private browsing modes for the majority of used browsers. This includes Safari, Firefox, IE10, Edge and Google Chrome.
Firefox
When the private browsing mode of Firefox is enabled, the IndexedDB throws an InvalidStateError because it is not available in private browsing mode.
To very if that:
var db = indexedDB.open("test");
db.onerror = function(){/*Firefox PB enabled*/};
db.onsuccess =function(){/*Not enabled*/};
Safari
For Safari, the key is the local storage service. It is disabled in privacy mode. So try to access it and use a try-catch clause.
The following method works on both, OSX and iOS devices. Credits for this method are going to this question and answer
var storage = window.sessionStorage;
try {
storage.setItem("someKeyHere", "test");
storage.removeItem("someKeyHere");
} catch (e) {
if (e.code === DOMException.QUOTA_EXCEEDED_ERR && storage.length === 0) {
//Private here
}
}
IE10/Edge
Internet Explore is even going to disable the IndexedDB when in privacy mode. So check for existence. But that's not sufficient enough, because older browsers maybe don't even have an IDB. So do another check, e.g. for events that only IE10 and subsequent browser have/trigger. A related question on CodeReview can be found here
if(!window.indexedDB && (window.PointerEvent || window.MSPointerEvent)){
//Privacy Mode
}
Chrome
Update: This doesn't work since Chrome 76 (thanks to #jLynx)
Chromes Incognito mode can be verified by the file system. A great explanation can be found here on SO
var fs = window.RequestFileSystem || window.webkitRequestFileSystem;
if (!fs) {
console.log("FS check failed..");
return;
}
fs(window.TEMPORARY, 100, function (fs) {}, function (err) {
//Incognito mode
});
update on safari
Safari (release v29) patched localstorage so that you can no longer use it for detection.
I saw one other solution that tries to save blob/files in indexedDB and that will reject with an error saying that it can't store fils when surfing in private mode.
but there is a new / easier method with the new whatwg/fs
navigator.storage.getDirectory().then(root => {
console.log('not private')
}, err => {
console.log('private')
})
Chrome 83 arrives with redesigned security settings, third-party cookies blocked in Incognito by default!
So this one is easy, create a iframe to a third party site, have it send a postMessage back notifying you if navigator.cookieEnabled is true or false. Ofc users have the option to disable third party cookie as well. So i tested and disabled 3th party cookies in the settings. But it still said cookie was enabled on third-party iframes using navigator.cookieEnabled. it only became disabled once i used Incognito - perhaps a bug?
new Promise((rs, rj, m = new MessageChannel(), d = document, i = d.createElement('iframe')) => {
i.src = 'https://httpbin.org/base64/PHNjcmlwdD5vbm1lc3NhZ2UgPSBlID0+IGUuZGF0YS5wb3N0TWVzc2FnZShuYXZpZ2F0b3IuY29va2llRW5hYmxlZCk8L3NjcmlwdD4='
i.onload = _ => i.contentWindow.postMessage(m.port1, '*', [m.port1], m.port2.onmessage = e => i.remove(rs(e.data)))
i.hidden = 1
d.body.append(i)
}).then(thirdPartyCookieEabled =>
console.log('Third party cookie enabled:', thirdPartyCookieEabled)
)
You could also probably do it using only js + ajax but didn't want to set up a 2 servers to test it myself. but for this SameSite=none have to be set as well.
res = await fetch('https://httpbin.org/cookies/set?enabled=1', {
credentials: 'include'
})
json = await res.json()
console.log(!!json.cookies.enabled)
Here is my take on detecting private mode
function detectPrivateMode(cb) {
var db,
on = cb.bind(null, true),
off = cb.bind(null, false)
function tryls() {
try {
localStorage.length ? off() : (localStorage.x = 1, localStorage.removeItem("x"), off());
} catch (e) {
// Safari only enables cookie in private mode
// if cookie is disabled then all client side storage is disabled
// if all client side storage is disabled, then there is no point
// in using private mode
navigator.cookieEnabled ? on() : off();
}
}
// Blink (chrome & opera)
window.webkitRequestFileSystem ? webkitRequestFileSystem(0, 0, off, on)
// FF
: "MozAppearance" in document.documentElement.style ? (db = indexedDB.open("test"), db.onerror = on, db.onsuccess = off)
// Safari
: /constructor/i.test(window.HTMLElement) || window.safari ? tryls()
// IE10+ & edge
: !window.indexedDB && (window.PointerEvent || window.MSPointerEvent) ? on()
// Rest
: off()
}
detectPrivateMode(function (isPrivateMode) {
console.log('is private mode: ' + isPrivateMode)
})
edit found a modern, faster, synkronas way to try it in firefox (they don't have service workers in privat mode) similar to ie don't include indexedDB but the test only works in secure sites
: "MozAppearance" in document.documentElement.style ? navigator.serviceWorker ? off() : on()
There's no way for your web page to know, absolutely for sure, that the user is in private browsing mode. Any attempts to check for various browser features will need to change often as security implementations are updated. It may work for some time in some browsers, but not all.
If the company is that concerned about security, I'd suggest rolling your own Firefox or Chromium distribution with locked down privacy settings, and only allowing that custom client to connect to the extranet.
I have built a little library that will work on all major platforms and browsers that I have tested: https://github.com/jLynx/PrivateWindowCheck
You can simply call
isPrivateWindow(function(is_private) {
if(is_private)
alert('Private');
else
alert('Not Private');
});
Many seem to believe that detecting incognito/private mode is no longer possible, and I can assure you that's definitely not true. I have been unable to find any publicly available code that works on 100% of browsers, so I made my own.
As of October 2021 my code is able to detect private browsing modes on Safari 15 (macOS & iOS), Chrome 93, Edge 93, Firefox 93, Internet Explorer 10+, as well as Brave.
I should also note that detecting incognito has legitimate, non-paywall related uses, of which I am using as part of a larger private project. For example, fraudsters often use incognito when conducting credit card fraud. While alone someone filling in a checkout form in incognito doesn't immediately scream fraud, paired with other data points (such as the user using a VPN as well), I would be wary of accepting such an order.
Sites that block access based on whether one is in incognito suck, but I pin that down more to bad site design.
Shameless plug: https://github.com/Joe12387/detectIncognito
I also have a demo available: https://detectincognito.com/
I hope this is helpful.
The localStorage trick is a bug which has been fixed, and it doesn't work anymore in Safari 11.0.
There is an interesting alternative that works in Safari, Opera and Internet Explorer (not Chrome): those browser send a DNT: 1 header (Do Not Track).
It's not 100% reliable because this header can be enabled for normal browsing (it's disabled by default), but it can help to identify privacy-conscious users.
Web browsers behave differently when privacy mode is activated.
On many browsers the caching of resources is limited. It is possible to detect where a browser has been based on their CSS cache. Its possible to conduct this this attack without JavaScript.
The EFF is working on a project to fingerprint browsers. Parts of the browsers fingerprint will be different when privacy mode is activated. Go ahead, try it.
You're not going to block them if they don't have private browsing enabled.
Why have a smart message box at all?
Would attempting to set a unique cookie work for all browsers and platforms? Anyone done this before?
I think the most elegant solution would be to:
Perform a security leak test
If security leak test reveals issue
Tell user to check settings
Suggest privacy mode
Because as you said, not everyone can or needs to enable privacy mode.
I agree with DigitalSeas's sentiment that we should generally not try to detect if the user is in a "private browsing" mode. However, I recently discovered that FireFox now subscribes to a service called "disconnect.me", which provides the url blacklist they use in their "tracking protection" feature. Since disconnect.me blacklists certain social networks (e.g. Facebook's facebook.net), we found that their SDKs would not load in FireFox. Therefore, it seems reasonable that we could try and detect private browsing mode in order to provide a more useful and precise error message to our users.
With that justification out of the way, this gist claims to provide detection for private browsing in major browsers using tricks specific to those browsers. At the time of this writing (the gist may have been updated by the time you read this) the detection logic is as follows:
function retry(isDone, next) {
var current_trial = 0, max_retry = 50, interval = 10, is_timeout = false;
var id = window.setInterval(
function() {
if (isDone()) {
window.clearInterval(id);
next(is_timeout);
}
if (current_trial++ > max_retry) {
window.clearInterval(id);
is_timeout = true;
next(is_timeout);
}
},
10
);
}
function isIE10OrLater(user_agent) {
var ua = user_agent.toLowerCase();
if (ua.indexOf('msie') === 0 && ua.indexOf('trident') === 0) {
return false;
}
var match = /(?:msie|rv:)\s?([\d\.]+)/.exec(ua);
if (match && parseInt(match[1], 10) >= 10) {
return true;
}
return false;
}
function detectPrivateMode(callback) {
var is_private;
if (window.webkitRequestFileSystem) {
window.webkitRequestFileSystem(
window.TEMPORARY, 1,
function() {
is_private = false;
},
function(e) {
console.log(e);
is_private = true;
}
);
} else if (window.indexedDB && /Firefox/.test(window.navigator.userAgent)) {
var db;
try {
db = window.indexedDB.open('test');
} catch(e) {
is_private = true;
}
if (typeof is_private === 'undefined') {
retry(
function isDone() {
return db.readyState === 'done' ? true : false;
},
function next(is_timeout) {
if (!is_timeout) {
is_private = db.result ? false : true;
}
}
);
}
} else if (isIE10OrLater(window.navigator.userAgent)) {
is_private = false;
try {
if (!window.indexedDB) {
is_private = true;
}
} catch (e) {
is_private = true;
}
} else if (window.localStorage && /Safari/.test(window.navigator.userAgent)) {
try {
window.localStorage.setItem('test', 1);
} catch(e) {
is_private = true;
}
if (typeof is_private === 'undefined') {
is_private = false;
window.localStorage.removeItem('test');
}
}
retry(
function isDone() {
return typeof is_private !== 'undefined' ? true : false;
},
function next(is_timeout) {
callback(is_private);
}
);
}
Browser makers are actively patching browser bugs that let sites identify private mode. Some bugs remain, but code to detect them is unlikely to be worth writing as the plan is it won't work for long.
The reason why browser makers are fixing these bugs is that sites like news publishers are using the bugs to identify private-mode users and refuse to serve them. The news publishers who want to do this tend to be using "metered paywalls", which let each browser view a certain number of free articles per day (or per week or whatever) and then start charging: since private-mode effectively resets the meter, the publishers don't want you to use private mode. But restricting private mode also restricts people who want to use it for other reasons, e.g. a domestic-abuse victim not wanting to keep history on a browser shared with their abuser, or someone not wanting their current activity to affect what advertisements they see in future, or some other reason. Although news sites that restrict private mode do tend to permit it if you log in, it's rather obvious that logging in lets them track you, so if you're trying to avoid being tracked then logging-in doesn't seem like an acceptable solution, hence browser makers want publishers not to be able to detect private mode.
There is another way of not keeping history: use a browser that lets you selectively clear it afterwards. Chrome for example can clear cookies, cache and history associated with specific domains, or for a specified period of time such as "for the last hour". That way you don't need private or incognito mode: you can get the same effect by browsing in normal mode and then erasing the traces. But you have to know how to do it, plus you must remember to do it, so it's a more troublesome approach than is using private mode. Still, I expect some people are using this as a workaround if their browser still has a bug that lets sites detect when they're in private mode and deny them service.
If you as a website author have a reason for not wanting your website to be stored in cache or history, for example if the site is meant to help domestic-abuse victims, then "how to detect private mode" might be the wrong question, as what you really want is "don't store this stuff in history, whether private mode is in use or not". Chris Coyier on css-tricks.com noted that window.location.replace() does not create a history item and techniques like this could be used to construct a site that at least doesn't leave its individual pages in history (at least not when browsed with Javascript switched on), but the referring page—possibly a dangerous-to-have search-result page—would still be in history, as would the final page they were on if they don't properly "sign out" of the site. (Intermediate pages might also be discernible via cache forensics, but anyone who can do that can probably also record the traffic as it happens, so we just have to hope the abuser is not that clever.) Another possible approach might be to create an innocent-looking cookie that stores the timestamp of the page's first load, and replace its content with something else if the current time exceeds that timestamp by too long, although its URL may still be loadable on another device or by clearing cookies (unless it was also session-dependent). But getting rid of the referring page is still going to be an issue, and as it's not always possible to confirm the user is in private mode, the best policy is probably to take steps to educate them about private mode anyway.
If you check their User-Agent, you can show them what both private mode and non private mode look like on their browser and OS, ask them to confirm which of the two matches their current window right now, and caution them if they choose the non private mode appearance. This won't work for browser and OS combinations you didn't test (but then neither will bug-based detection), and it assumes the user cares about their security enough to take the question seriously—but if they don't, the battle is probably already lost anyway.
Well, you wouldn't really distinguish private mode from "block all cookies" in that way, but apart from that rare situation I guess it should work.
The big problem IMO, is that this is a very very bad site design, not better than the good ol' "you need browser xxx to see this website" that was common in the '90s. Not all browser have a Private Browsing mode (as much as I despise IE, your cutting out IE7 users for instance) and those users won't be able to access your site at all.
Also, when I'm on the Internet I often have several tabs open with multiple website. It would be really annoying for me to have to switch to private mode just to see that website and not being able to access the other sites at the same time.
One thing you could do would be designing the site using sessions instead of cookies, so they won't be stored (as you don't use them...). And as for the history... really, what's the problem with that?
I've solved this issue by using two HTML pages. The main page define a status variable and set a cookie. The second page is opened in a new window (not tab), read the cookie and set status to the cookie value. Under MSIE, the cookie value is passed to the child page when the main page in normal mode. When in InPrivate Browsing mode, the cookie value is not passed to the child page (but is passed if you open a new tab).
The main.html page:
<script>
var myCookie="nocookie";
document.cookie="checkInPrivate=1";
var h=window.open("child.html", "_blank", "left=9999,height=200,width=200");
setTimeout(function() {
var status=null;
if (myCookie=="nocookie") {
status="unable to determine if we are InPrivate Browsing mode (child page did not set the cookie)";
} else if (myCookie.indexOf("checkInPrivate")>=0) {
status="not in InPrivate Browsing mode (child page did set the cookie)";
} else {
status="in InPrivate Browsing mode (child page set the cookie value but it was not provided)";
}
alert(status);
}, 200);
</script>
The child.html page:
Detecting MSIE's InPrivate Browsing mode...
<script>
window.opener.myCookie=document.cookie;
window.close();
</script>
I'm using InPrivate Browsing mode in order to prevent Browser Helper Objects (BHO) and browser extensions to be enabled, since BHO are most often malwares which can modify the web pages even if HTTPS and strong authentication are used. Internet Explorer 9 has a "Disable toolbars and extensions when InPrivate Browsing starts" in its "Privacy" settings.
However, this is not the ultimate way to prevent malicious browser extension: a malicious extension may change the main page behavior to make it think that the myCookie value has not been set and. We would wrongly assume that we are in InPrivate Browsing mode.
Note that I need cookies for my application so I do not use InPrivate Browsing for that purpose.
Write code to achieve following
1) In firefox test browser version. This method works with version >= 33.0 ( supports service workers ). Cannot use this method with old ( < 33.0 ) versions.
2) Try to set service worker.
3) If you can set,use or access a service worker you are 1000% not in private browsing mode as service workers cannot be interacted with on Firefox private browsing mode. I wish that they could be.
Quote:
"In Firefox, Service Worker APIs are hidden and cannot be used when the user is in private browsing mode"
https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers
Not sure if its cause this question is old but Firefox does provide documentation on how to detect private browsing mode. However it involves using an import of theirs DXR PrivateBrowsingUtils:
try {
// Firefox 20+
Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
...
}
} catch(e) {
// pre Firefox 20 (if you do not have access to a doc.
// might use doc.hasAttribute("privatebrowsingmode") then instead)
try {
var inPrivateBrowsing = Components.classes["#mozilla.org/privatebrowsing;1"].
getService(Components.interfaces.nsIPrivateBrowsingService).
privateBrowsingEnabled;
if (!inPrivateBrowsing) {
...
}
} catch(e) {
Components.utils.reportError(e);
return;
}
}
While creating my Safari extension, I found out that it was possible to query the boolean safari.self.browserWindow.activeTab.private. Below worked for me to check whether the browser was open in Private or not but only from the extension.
isPrivate = false;
try {
isPrivate = safari.self.browserWindow.activeTab.private;
} catch (_) {
isPrivate = true;
}
if (isPrivate === true){
console.log("Private window.");}
else {
console.log("Not private window.");}
Source: developer.apple.com | Instance Property private
function isPrivate(callback) {
callback || (callback = function(){});
var fs = window.RequestFileSystem || window.webkitRequestFileSystem;
if(fs){
return fs(window.TEMPORARY, 1, callback.bind(this, false), callback.bind(this, true));
}
if(window.indexedDB && /Firefox/.test(window.navigator.userAgent)){
try {
var db = window.indexedDB.open('test');
var tryes = 0;
var interval = limit = 10;
var wait = function(check){
if(tryes >= limit){ return callback(true); } // Give up
return window.setTimeout(check, ++tryes * interval);
}
var evaluate = function(){
return db.readyState === 'done' ? callback(!db.result) : wait(evaluate);
}
return wait(evaluate);
} catch (e) {
return callback(true);
}
}
if (!!window.navigator.userAgent.match(/(MSIE|Trident|Edge)/)){
try {
return callback(!window.indexedDB);
} catch (e) {
return callback(true);
}
}
try {
window.openDatabase(null, null, null, null);
return callback(false);
} catch (e) {
return callback(true);
}
}
isPrivate( function(isPrivate) {
console.log('Private mode ===>', isPrivate);
});
You can find this gist to detect private browsing
var hasStorage = function() {
var mod,
result;
try {
mod = new Date;
localStorage.setItem(mod, mod.toString());
result = localStorage.getItem(mod) === mod.toString();
localStorage.removeItem(mod);
return result;
} catch (_error) {
return false;
}
},
hasCookies = function() {
var cookieEnabled = navigator.cookieEnabled ? true : false;
if (typeof navigator.cookieEnabled == 'undefined' && !cookieEnabled) {
document.cookie = 'testcookie';
cookieEnabled = (document.cookie.indexOf('testcookie') != -1) ? true : false;
}
return cookieEnabled;
};
if (!hasStorage()) {
document.getElementById('apple_storage').style.display = 'block';
} else if (!hasCookies()) {
document.getElementById('no_cookie').style.display = 'block';
}
<!-- ERROR FOR BLOCKED LOCAL STORAGE -->
<div id="apple_storage" style="display:none;">
<div class="modal-new alerts">
<h2>Attenion</h2>
Close
<div class="body">
<p>
Dear customer, to ensure proper use of the site it is necessary to abandon the
private browsing.
</p>
</div><!-- /.body -->
</div>
</div>
<!-- ERROR FOR BLOCKED COOKIES -->
<div id="no_cookie" style="display:none;">
<div class="modal-new alerts">
<h2>Attenion</h2>
Close
<div class="body">
<p>
Dear customer, to ensure proper use of the site it is necessary to enable cookies.
</p>
</div><!-- /.body -->
</div>
</div>
Related
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.
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"));
}
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')
}
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.
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.
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.
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.
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
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') });
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)
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/
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."); }
I have this problem where in firefox the speech gets cut off if the page is auto-refreshed, but in google chrome it finishes saying the speech even if the page is auto-refreshed. How do I fix it so that the speech doesn't get cut off in firefox even when the page is auto-refreshed?
msg = new SpeechSynthesisUtterance("please finish saying this entire sentence.");
window.speechSynthesis.speak(msg);
(function ($) {
'use strict';
if (window == window.top) {
var body = $('body').empty();
var myframe = $('<iframe>')
.attr({ src: location.href })
.css({ height: '95vh', width: '100%' })
.appendTo(body)
.on('load', function () {
var interval;
interval = 750;
setTimeout(function () {
myframe.attr({ src: location.href });
}, interval);
});
}
})(jQuery);
I have this problem where in firefox the speech gets cut off if the
page is auto-refreshed, but in google chrome it finishes saying the
speech even if the page is auto-refreshed.
The described behaviour for Firefox is a sane expected implementation.
Browsing the source code of Firefox and Chromium the implementation of speechSynthesis.speak() is based on a socket connection with the local speech server. That server at *nix is usually speech-dispatcher or speechd (speech-dispatcher). See How to programmatically send a unix socket command to a system server autospawned by browser or convert JavaScript to C++ souce code for Chromium? for description of trying to implement SSML parsing at Chromium.
Eventually decided to write own code to achieve that requirement using JavaScript according to the W3C specification SpeechSynthesisSSMLParser after asking more than one question at SE sites, filing issues and bugs and posting on W3C mailings lists without any evidence that SSML parsing would ever be included as part of the Web Speech API.
Once that connection is initiated a queue is created for calls to .speak(). Even when the connection is closed Task Manager might still show the active process registered by the service.
The process at Chromium/Chrome is not without bugs, the closest that have filed to what is being described at the question is
Issue 797624: "speak speak slash" is audio output of .speak() following two calls to .speak(), .pause() and .resume()
Why hasn't Issue 88072 and Issue 795371 been answered? Are Internals>SpeechSynthesis and Blink>Speech dead? (for possible reason why "but in google chrome it finishes saying the speech even if the page is auto-refreshed." is still possible at Chrome)
.volume property issues
Issue 797512: Setting SpeechSynthesisUtterance.volume does not change volume of audio output of speechSynthesis.speak() (Chromium/Chrome)
Bug 1426978 Setting SpeechSynthesisUtterance.volume does not change volume of audio output of speechSynthesis.speak() (Firefox)
The most egregious issue being Chromium/Chrome webkitSpeechReconition implementation which records the users' audio and posts that audio data to a remote service, where a transcript is returned to the browser - without explicitly notifying the user that is taking place, marked WONT FIX
Issue 816095: Does webkitSpeechRecognition send recorded audio to a remote web service by default?
Relevant W3C Speech API issues at GitHub
The UA should be able to disallow speak() from autoplaying #27
Precisely define when speak() should fail due to autoplay rules #35 (ironically, relevant to the reported behaviour at Chromium/Chrome and output described at this question, see Web Audio, Autoplay Policy and Games and Autoplay Policy Changes)
Intent to Deprecate: speechSynthesis.speak without user activation
Summary
The SpeechSynthesis API is actively being abused on the web. We don’t have hard data on abuse, but since other autoplay avenues are
starting to be closed, abuse is anecdotally moving to the Web Speech
API, which doesn't follow autoplay rules.
After deprecation, the plan is to cause speechSynthesis.speak to
immediately fire an error if specific autoplay rules are not
satisfied. This will align it with other audio APIs in Chrome.
Timing of SpeechSynthesis state changes not defined #39
Timing of SpeechSynthesisUtterance events firing not defined #40
Clarify what happens if two windows try to speak #47
In summary, would not describe the behaviour at Firefox as a "problem", but the behaviour at Chrome as being a potential "problem".
Diving in to W3C Web Speech API implementation at browsers is not a trivial task. For several reasons. Including the apparent focus, or available option of, commercial TTS/SST services and proprietary, closed-source implementations of speech synthesis and speech recognition in "smart phones"; in lieu of fixing the various issues with the actual deployment of the W3C Web Speech API at modern browsers.
The maintainers of speechd (speech-dispatcher) are very helpful with regards to the server side (local speech-dispatcher socket).
Cannot speak for Firefox maintainers. Would estimate it is unlikely that if a bug is filed relevant to the feature request of continuing execution of audio output by .speak() from reloaded window is consistent with recent autoplay policies implemented by browsers. Though you can still file a Firefox bug to ask if audio output (from any API or interface) is expected to continue during reload of the current window; and if there are any preferences or policies which can be set to override the described behaviour, as suggested at the answer by #zip. And get the answer from the implementers themselves.
There are individuals and groups that compose FOSS code which are active in the domain and willing to help SST/TTS development, many of which are active at GitHub, which is another option to ask questions about how to implement what you are trying to achieve specifically at Firefox browser.
Outside of asking implementers for the feature request, you can read the source code and try create one or more workarounds. Alternatives include using meSpeak.js, though that still does not necessarily address if Firefox is intentionally blocking audio output during reload of the window.
Not sure why there's a difference in behavior... guest271314 might be on to something in his answer. However, you may be able to prevent FF from stopping the tts by intercepting the reload event with a onbeforeunload handler and waiting for the utterance to finish:
msg = new SpeechSynthesisUtterance("say something");
window.speechSynthesis.speak(msg);
window.onbeforeunload = function(e) {
if(window.speechSynthesis.speaking){
event.preventDefault();
msg.addEventListener('end', function(event) {
//logic to continue unload here
});
}
};
EDITED: See more elegant solution with promises below initial answer!
Below snippet is a workaround to the browser inconsistencies found in Firefox, checking synth.speaking in the interval and only triggering a reload if it's false prevents the synth from cutting of prematurely:
(It does not NOT work properly in the SO snippet, I assume it doesn't like iFrames in iFrames or whatever, just copy paste the code in a file and open it with Firefox!)
<p>I'm in the body, but will be in an iFrame</p>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
var synth = window.speechSynthesis;
msg = new SpeechSynthesisUtterance("please finish saying this entire sentence.");
synth.speak(msg);
(function ($) {
'use strict';
if (window == window.top) {
var body = $('body').empty();
var myframe = $('<iframe>')
.attr({ src: location.href })
.css({ height: '95vh', width: '100%' })
.appendTo(body)
.on('load', function () {
var interval;
interval = setInterval(function () {
if (!synth.speaking) {
myframe.attr({ src: location.href });
clearInterval(interval);
}
}, 750);
});
}
})(jQuery);
</script>
A more elaborate solution could be to not have any setTimeout() or setInterval() at all, but use promises instead. Like this the page will simply reload whenever the message is done synthesizing, no matter how short or long it is. This will also prevent the "double"/overlapping-speech on the initial pageload. Not sure if this helps in your scenario, but here you go:
<button id="toggleSpeech">Stop Speaking!</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
if (window == window.top) {
window.speech = {
say: function(msg) {
return new Promise(function(resolve, reject) {
if (!SpeechSynthesisUtterance) {
reject('Web Speech API is not supported');
}
var utterance = new SpeechSynthesisUtterance(msg);
utterance.addEventListener('end', function() {
resolve();
});
utterance.addEventListener('error', function(event) {
reject('An error has occurred while speaking: ' + event.error);
});
window.speechSynthesis.speak(utterance);
});
},
speak: true,
};
}
(function($) {
'use strict';
if (window == window.top) {
var body = $('body').empty();
var myframe = $('<iframe>')
.attr({ src: location.href })
.css({ height: '95vh', width: '100%' })
.appendTo(body)
.on('load', function () {
var $iframe = $(this).contents();
$iframe.find('#toggleSpeech').on('click', function(e) {
console.log('speaking will stop when the last sentence is done...');
window.speech.speak = !window.speech.speak;
});
window.speech.say('please finish saying this entire sentence.')
.then(function() {
if ( window.speech.speak ) {
console.log('speaking done, reloading iframe!');
myframe.attr({ src: location.href });
}
});
});
}
})(jQuery);
</script>
NOTE: Chrome (since v70) does NOT allow the immediate calling of window.speechSynthesis.speak(new SpeechSynthesisUtterance(msg)) anymore, you will get an error speechSynthesis.speak() without user activation is no longer allowed..., more details here. So technically the user would have to activate the script in Chrome to make it work!
Firefox:
First of all type and search for the “about: config” inside the browser by filling it in the address bar. This will take to another page where there will be a pop up asking to Take Any Risk, you need to accept that. Look for the preference named “accessibility.blockautorefresh” from the list and then right-click over that. There will be some options appearing as the list on the screen, select the Toggle option and then set it to True rather than False. This change will block the Auto Refresh on the Firefox browser. Remember that this option is revertable!
I'm building an extranet for a company paranoid about security. They want to make sure that (among other things) their users are browsing the site with the Private Browsing mode switched on in their web browser so that no cookies or history is kept.
I found only this
http://jeremiahgrossman.blogspot.com/2009/03/detecting-private-browsing-mode.html
and
https://serverfault.com/questions/18966/force-safari-to-operate-in-private-mode-and-detect-that-state-from-a-webserver
The ideal solution would use no or minimal javascript. Would attempting to set a unique cookie work for all browsers and platforms? Anyone done this before?
thanks!
update
http://crypto.stanford.edu/~collinj/research/incognito/ uses the CSS visited technique of the browser fingerprinters mentioned by other posters- thanks for the hints.
I like it because it is small and elegant, but still want to be able to do it without javascript if possible.
Update June 2019
Google is removing the ability to detect Private Browsing Mode permanently in Chrome 76 onwards. So, if you're wanting to detect private browsing it's now impossible (unless you find a way to do it that Google hasn't found). The ability to detect private browsing mode has been acknowledged as a bug and was never intended.
To anyone else coming across this question, please note as of 2014, there is no reliable or accurate way to detect if someone is browsing in an incognito/private/safe browsing mode through Javascript or CSS. Previous solutions that once worked like the CSS history hack have since been rendered unusable by all browser vendors.
There should never be a situation where needing to detect private browsing mode on a normal day-to-day website is ever needed. People are choosing to browse anonymously and or not anonymously for their own reasons.
Browsers like Chrome and Firefox do not disable functionality like localStorage any more. They simply namespace it in a temporary location to prevent websites that use it from erroring out. Once you're finished browsing, the namespace is erased and nothing is saved. If you are testing for localStorage support regardless of mode, it will always return true for browsers that support it.
Other means of detecting private mode in Chrome specifically have been completely patched and will no longer work.
If it is required internally by a company, you should develop a browser plugin. Chrome and Firefox, in particular, expose internal API's which allow plugins to check if the user is in private browsing/incognito mode and action accordingly. It cannot be done outside of a plugin.
Here's an easier way to do detect privacy mode. This works in Safari only. I created it because a web app I am developing uses localStorage. LocalStorage is not available in Safari when in privacy mode, thus my app will not work.
On page load, run the script below. It shows an alert box if we cannot use localStorage.
try {
// try to use localStorage
localStorage.test = 2;
} catch (e) {
// there was an error so...
alert('You are in Privacy Mode\nPlease deactivate Privacy Mode and then reload the page.');
}
Current state
Google Chrome has developed further and leaves no more space for detection when using incognito mode. Same might apply for other browsers.
Old solutions (might partially work)
It is possible to detect enabled private browsing modes for the majority of used browsers. This includes Safari, Firefox, IE10, Edge and Google Chrome.
Firefox
When the private browsing mode of Firefox is enabled, the IndexedDB throws an InvalidStateError because it is not available in private browsing mode.
To very if that:
var db = indexedDB.open("test");
db.onerror = function(){/*Firefox PB enabled*/};
db.onsuccess =function(){/*Not enabled*/};
Safari
For Safari, the key is the local storage service. It is disabled in privacy mode. So try to access it and use a try-catch clause.
The following method works on both, OSX and iOS devices. Credits for this method are going to this question and answer
var storage = window.sessionStorage;
try {
storage.setItem("someKeyHere", "test");
storage.removeItem("someKeyHere");
} catch (e) {
if (e.code === DOMException.QUOTA_EXCEEDED_ERR && storage.length === 0) {
//Private here
}
}
IE10/Edge
Internet Explore is even going to disable the IndexedDB when in privacy mode. So check for existence. But that's not sufficient enough, because older browsers maybe don't even have an IDB. So do another check, e.g. for events that only IE10 and subsequent browser have/trigger. A related question on CodeReview can be found here
if(!window.indexedDB && (window.PointerEvent || window.MSPointerEvent)){
//Privacy Mode
}
Chrome
Update: This doesn't work since Chrome 76 (thanks to #jLynx)
Chromes Incognito mode can be verified by the file system. A great explanation can be found here on SO
var fs = window.RequestFileSystem || window.webkitRequestFileSystem;
if (!fs) {
console.log("FS check failed..");
return;
}
fs(window.TEMPORARY, 100, function (fs) {}, function (err) {
//Incognito mode
});
update on safari
Safari (release v29) patched localstorage so that you can no longer use it for detection.
I saw one other solution that tries to save blob/files in indexedDB and that will reject with an error saying that it can't store fils when surfing in private mode.
but there is a new / easier method with the new whatwg/fs
navigator.storage.getDirectory().then(root => {
console.log('not private')
}, err => {
console.log('private')
})
Chrome 83 arrives with redesigned security settings, third-party cookies blocked in Incognito by default!
So this one is easy, create a iframe to a third party site, have it send a postMessage back notifying you if navigator.cookieEnabled is true or false. Ofc users have the option to disable third party cookie as well. So i tested and disabled 3th party cookies in the settings. But it still said cookie was enabled on third-party iframes using navigator.cookieEnabled. it only became disabled once i used Incognito - perhaps a bug?
new Promise((rs, rj, m = new MessageChannel(), d = document, i = d.createElement('iframe')) => {
i.src = 'https://httpbin.org/base64/PHNjcmlwdD5vbm1lc3NhZ2UgPSBlID0+IGUuZGF0YS5wb3N0TWVzc2FnZShuYXZpZ2F0b3IuY29va2llRW5hYmxlZCk8L3NjcmlwdD4='
i.onload = _ => i.contentWindow.postMessage(m.port1, '*', [m.port1], m.port2.onmessage = e => i.remove(rs(e.data)))
i.hidden = 1
d.body.append(i)
}).then(thirdPartyCookieEabled =>
console.log('Third party cookie enabled:', thirdPartyCookieEabled)
)
You could also probably do it using only js + ajax but didn't want to set up a 2 servers to test it myself. but for this SameSite=none have to be set as well.
res = await fetch('https://httpbin.org/cookies/set?enabled=1', {
credentials: 'include'
})
json = await res.json()
console.log(!!json.cookies.enabled)
Here is my take on detecting private mode
function detectPrivateMode(cb) {
var db,
on = cb.bind(null, true),
off = cb.bind(null, false)
function tryls() {
try {
localStorage.length ? off() : (localStorage.x = 1, localStorage.removeItem("x"), off());
} catch (e) {
// Safari only enables cookie in private mode
// if cookie is disabled then all client side storage is disabled
// if all client side storage is disabled, then there is no point
// in using private mode
navigator.cookieEnabled ? on() : off();
}
}
// Blink (chrome & opera)
window.webkitRequestFileSystem ? webkitRequestFileSystem(0, 0, off, on)
// FF
: "MozAppearance" in document.documentElement.style ? (db = indexedDB.open("test"), db.onerror = on, db.onsuccess = off)
// Safari
: /constructor/i.test(window.HTMLElement) || window.safari ? tryls()
// IE10+ & edge
: !window.indexedDB && (window.PointerEvent || window.MSPointerEvent) ? on()
// Rest
: off()
}
detectPrivateMode(function (isPrivateMode) {
console.log('is private mode: ' + isPrivateMode)
})
edit found a modern, faster, synkronas way to try it in firefox (they don't have service workers in privat mode) similar to ie don't include indexedDB but the test only works in secure sites
: "MozAppearance" in document.documentElement.style ? navigator.serviceWorker ? off() : on()
There's no way for your web page to know, absolutely for sure, that the user is in private browsing mode. Any attempts to check for various browser features will need to change often as security implementations are updated. It may work for some time in some browsers, but not all.
If the company is that concerned about security, I'd suggest rolling your own Firefox or Chromium distribution with locked down privacy settings, and only allowing that custom client to connect to the extranet.
I have built a little library that will work on all major platforms and browsers that I have tested: https://github.com/jLynx/PrivateWindowCheck
You can simply call
isPrivateWindow(function(is_private) {
if(is_private)
alert('Private');
else
alert('Not Private');
});
Many seem to believe that detecting incognito/private mode is no longer possible, and I can assure you that's definitely not true. I have been unable to find any publicly available code that works on 100% of browsers, so I made my own.
As of October 2021 my code is able to detect private browsing modes on Safari 15 (macOS & iOS), Chrome 93, Edge 93, Firefox 93, Internet Explorer 10+, as well as Brave.
I should also note that detecting incognito has legitimate, non-paywall related uses, of which I am using as part of a larger private project. For example, fraudsters often use incognito when conducting credit card fraud. While alone someone filling in a checkout form in incognito doesn't immediately scream fraud, paired with other data points (such as the user using a VPN as well), I would be wary of accepting such an order.
Sites that block access based on whether one is in incognito suck, but I pin that down more to bad site design.
Shameless plug: https://github.com/Joe12387/detectIncognito
I also have a demo available: https://detectincognito.com/
I hope this is helpful.
The localStorage trick is a bug which has been fixed, and it doesn't work anymore in Safari 11.0.
There is an interesting alternative that works in Safari, Opera and Internet Explorer (not Chrome): those browser send a DNT: 1 header (Do Not Track).
It's not 100% reliable because this header can be enabled for normal browsing (it's disabled by default), but it can help to identify privacy-conscious users.
Web browsers behave differently when privacy mode is activated.
On many browsers the caching of resources is limited. It is possible to detect where a browser has been based on their CSS cache. Its possible to conduct this this attack without JavaScript.
The EFF is working on a project to fingerprint browsers. Parts of the browsers fingerprint will be different when privacy mode is activated. Go ahead, try it.
You're not going to block them if they don't have private browsing enabled.
Why have a smart message box at all?
Would attempting to set a unique cookie work for all browsers and platforms? Anyone done this before?
I think the most elegant solution would be to:
Perform a security leak test
If security leak test reveals issue
Tell user to check settings
Suggest privacy mode
Because as you said, not everyone can or needs to enable privacy mode.
I agree with DigitalSeas's sentiment that we should generally not try to detect if the user is in a "private browsing" mode. However, I recently discovered that FireFox now subscribes to a service called "disconnect.me", which provides the url blacklist they use in their "tracking protection" feature. Since disconnect.me blacklists certain social networks (e.g. Facebook's facebook.net), we found that their SDKs would not load in FireFox. Therefore, it seems reasonable that we could try and detect private browsing mode in order to provide a more useful and precise error message to our users.
With that justification out of the way, this gist claims to provide detection for private browsing in major browsers using tricks specific to those browsers. At the time of this writing (the gist may have been updated by the time you read this) the detection logic is as follows:
function retry(isDone, next) {
var current_trial = 0, max_retry = 50, interval = 10, is_timeout = false;
var id = window.setInterval(
function() {
if (isDone()) {
window.clearInterval(id);
next(is_timeout);
}
if (current_trial++ > max_retry) {
window.clearInterval(id);
is_timeout = true;
next(is_timeout);
}
},
10
);
}
function isIE10OrLater(user_agent) {
var ua = user_agent.toLowerCase();
if (ua.indexOf('msie') === 0 && ua.indexOf('trident') === 0) {
return false;
}
var match = /(?:msie|rv:)\s?([\d\.]+)/.exec(ua);
if (match && parseInt(match[1], 10) >= 10) {
return true;
}
return false;
}
function detectPrivateMode(callback) {
var is_private;
if (window.webkitRequestFileSystem) {
window.webkitRequestFileSystem(
window.TEMPORARY, 1,
function() {
is_private = false;
},
function(e) {
console.log(e);
is_private = true;
}
);
} else if (window.indexedDB && /Firefox/.test(window.navigator.userAgent)) {
var db;
try {
db = window.indexedDB.open('test');
} catch(e) {
is_private = true;
}
if (typeof is_private === 'undefined') {
retry(
function isDone() {
return db.readyState === 'done' ? true : false;
},
function next(is_timeout) {
if (!is_timeout) {
is_private = db.result ? false : true;
}
}
);
}
} else if (isIE10OrLater(window.navigator.userAgent)) {
is_private = false;
try {
if (!window.indexedDB) {
is_private = true;
}
} catch (e) {
is_private = true;
}
} else if (window.localStorage && /Safari/.test(window.navigator.userAgent)) {
try {
window.localStorage.setItem('test', 1);
} catch(e) {
is_private = true;
}
if (typeof is_private === 'undefined') {
is_private = false;
window.localStorage.removeItem('test');
}
}
retry(
function isDone() {
return typeof is_private !== 'undefined' ? true : false;
},
function next(is_timeout) {
callback(is_private);
}
);
}
Browser makers are actively patching browser bugs that let sites identify private mode. Some bugs remain, but code to detect them is unlikely to be worth writing as the plan is it won't work for long.
The reason why browser makers are fixing these bugs is that sites like news publishers are using the bugs to identify private-mode users and refuse to serve them. The news publishers who want to do this tend to be using "metered paywalls", which let each browser view a certain number of free articles per day (or per week or whatever) and then start charging: since private-mode effectively resets the meter, the publishers don't want you to use private mode. But restricting private mode also restricts people who want to use it for other reasons, e.g. a domestic-abuse victim not wanting to keep history on a browser shared with their abuser, or someone not wanting their current activity to affect what advertisements they see in future, or some other reason. Although news sites that restrict private mode do tend to permit it if you log in, it's rather obvious that logging in lets them track you, so if you're trying to avoid being tracked then logging-in doesn't seem like an acceptable solution, hence browser makers want publishers not to be able to detect private mode.
There is another way of not keeping history: use a browser that lets you selectively clear it afterwards. Chrome for example can clear cookies, cache and history associated with specific domains, or for a specified period of time such as "for the last hour". That way you don't need private or incognito mode: you can get the same effect by browsing in normal mode and then erasing the traces. But you have to know how to do it, plus you must remember to do it, so it's a more troublesome approach than is using private mode. Still, I expect some people are using this as a workaround if their browser still has a bug that lets sites detect when they're in private mode and deny them service.
If you as a website author have a reason for not wanting your website to be stored in cache or history, for example if the site is meant to help domestic-abuse victims, then "how to detect private mode" might be the wrong question, as what you really want is "don't store this stuff in history, whether private mode is in use or not". Chris Coyier on css-tricks.com noted that window.location.replace() does not create a history item and techniques like this could be used to construct a site that at least doesn't leave its individual pages in history (at least not when browsed with Javascript switched on), but the referring page—possibly a dangerous-to-have search-result page—would still be in history, as would the final page they were on if they don't properly "sign out" of the site. (Intermediate pages might also be discernible via cache forensics, but anyone who can do that can probably also record the traffic as it happens, so we just have to hope the abuser is not that clever.) Another possible approach might be to create an innocent-looking cookie that stores the timestamp of the page's first load, and replace its content with something else if the current time exceeds that timestamp by too long, although its URL may still be loadable on another device or by clearing cookies (unless it was also session-dependent). But getting rid of the referring page is still going to be an issue, and as it's not always possible to confirm the user is in private mode, the best policy is probably to take steps to educate them about private mode anyway.
If you check their User-Agent, you can show them what both private mode and non private mode look like on their browser and OS, ask them to confirm which of the two matches their current window right now, and caution them if they choose the non private mode appearance. This won't work for browser and OS combinations you didn't test (but then neither will bug-based detection), and it assumes the user cares about their security enough to take the question seriously—but if they don't, the battle is probably already lost anyway.
Well, you wouldn't really distinguish private mode from "block all cookies" in that way, but apart from that rare situation I guess it should work.
The big problem IMO, is that this is a very very bad site design, not better than the good ol' "you need browser xxx to see this website" that was common in the '90s. Not all browser have a Private Browsing mode (as much as I despise IE, your cutting out IE7 users for instance) and those users won't be able to access your site at all.
Also, when I'm on the Internet I often have several tabs open with multiple website. It would be really annoying for me to have to switch to private mode just to see that website and not being able to access the other sites at the same time.
One thing you could do would be designing the site using sessions instead of cookies, so they won't be stored (as you don't use them...). And as for the history... really, what's the problem with that?
I've solved this issue by using two HTML pages. The main page define a status variable and set a cookie. The second page is opened in a new window (not tab), read the cookie and set status to the cookie value. Under MSIE, the cookie value is passed to the child page when the main page in normal mode. When in InPrivate Browsing mode, the cookie value is not passed to the child page (but is passed if you open a new tab).
The main.html page:
<script>
var myCookie="nocookie";
document.cookie="checkInPrivate=1";
var h=window.open("child.html", "_blank", "left=9999,height=200,width=200");
setTimeout(function() {
var status=null;
if (myCookie=="nocookie") {
status="unable to determine if we are InPrivate Browsing mode (child page did not set the cookie)";
} else if (myCookie.indexOf("checkInPrivate")>=0) {
status="not in InPrivate Browsing mode (child page did set the cookie)";
} else {
status="in InPrivate Browsing mode (child page set the cookie value but it was not provided)";
}
alert(status);
}, 200);
</script>
The child.html page:
Detecting MSIE's InPrivate Browsing mode...
<script>
window.opener.myCookie=document.cookie;
window.close();
</script>
I'm using InPrivate Browsing mode in order to prevent Browser Helper Objects (BHO) and browser extensions to be enabled, since BHO are most often malwares which can modify the web pages even if HTTPS and strong authentication are used. Internet Explorer 9 has a "Disable toolbars and extensions when InPrivate Browsing starts" in its "Privacy" settings.
However, this is not the ultimate way to prevent malicious browser extension: a malicious extension may change the main page behavior to make it think that the myCookie value has not been set and. We would wrongly assume that we are in InPrivate Browsing mode.
Note that I need cookies for my application so I do not use InPrivate Browsing for that purpose.
Write code to achieve following
1) In firefox test browser version. This method works with version >= 33.0 ( supports service workers ). Cannot use this method with old ( < 33.0 ) versions.
2) Try to set service worker.
3) If you can set,use or access a service worker you are 1000% not in private browsing mode as service workers cannot be interacted with on Firefox private browsing mode. I wish that they could be.
Quote:
"In Firefox, Service Worker APIs are hidden and cannot be used when the user is in private browsing mode"
https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers
Not sure if its cause this question is old but Firefox does provide documentation on how to detect private browsing mode. However it involves using an import of theirs DXR PrivateBrowsingUtils:
try {
// Firefox 20+
Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
...
}
} catch(e) {
// pre Firefox 20 (if you do not have access to a doc.
// might use doc.hasAttribute("privatebrowsingmode") then instead)
try {
var inPrivateBrowsing = Components.classes["#mozilla.org/privatebrowsing;1"].
getService(Components.interfaces.nsIPrivateBrowsingService).
privateBrowsingEnabled;
if (!inPrivateBrowsing) {
...
}
} catch(e) {
Components.utils.reportError(e);
return;
}
}
While creating my Safari extension, I found out that it was possible to query the boolean safari.self.browserWindow.activeTab.private. Below worked for me to check whether the browser was open in Private or not but only from the extension.
isPrivate = false;
try {
isPrivate = safari.self.browserWindow.activeTab.private;
} catch (_) {
isPrivate = true;
}
if (isPrivate === true){
console.log("Private window.");}
else {
console.log("Not private window.");}
Source: developer.apple.com | Instance Property private
function isPrivate(callback) {
callback || (callback = function(){});
var fs = window.RequestFileSystem || window.webkitRequestFileSystem;
if(fs){
return fs(window.TEMPORARY, 1, callback.bind(this, false), callback.bind(this, true));
}
if(window.indexedDB && /Firefox/.test(window.navigator.userAgent)){
try {
var db = window.indexedDB.open('test');
var tryes = 0;
var interval = limit = 10;
var wait = function(check){
if(tryes >= limit){ return callback(true); } // Give up
return window.setTimeout(check, ++tryes * interval);
}
var evaluate = function(){
return db.readyState === 'done' ? callback(!db.result) : wait(evaluate);
}
return wait(evaluate);
} catch (e) {
return callback(true);
}
}
if (!!window.navigator.userAgent.match(/(MSIE|Trident|Edge)/)){
try {
return callback(!window.indexedDB);
} catch (e) {
return callback(true);
}
}
try {
window.openDatabase(null, null, null, null);
return callback(false);
} catch (e) {
return callback(true);
}
}
isPrivate( function(isPrivate) {
console.log('Private mode ===>', isPrivate);
});
You can find this gist to detect private browsing
var hasStorage = function() {
var mod,
result;
try {
mod = new Date;
localStorage.setItem(mod, mod.toString());
result = localStorage.getItem(mod) === mod.toString();
localStorage.removeItem(mod);
return result;
} catch (_error) {
return false;
}
},
hasCookies = function() {
var cookieEnabled = navigator.cookieEnabled ? true : false;
if (typeof navigator.cookieEnabled == 'undefined' && !cookieEnabled) {
document.cookie = 'testcookie';
cookieEnabled = (document.cookie.indexOf('testcookie') != -1) ? true : false;
}
return cookieEnabled;
};
if (!hasStorage()) {
document.getElementById('apple_storage').style.display = 'block';
} else if (!hasCookies()) {
document.getElementById('no_cookie').style.display = 'block';
}
<!-- ERROR FOR BLOCKED LOCAL STORAGE -->
<div id="apple_storage" style="display:none;">
<div class="modal-new alerts">
<h2>Attenion</h2>
Close
<div class="body">
<p>
Dear customer, to ensure proper use of the site it is necessary to abandon the
private browsing.
</p>
</div><!-- /.body -->
</div>
</div>
<!-- ERROR FOR BLOCKED COOKIES -->
<div id="no_cookie" style="display:none;">
<div class="modal-new alerts">
<h2>Attenion</h2>
Close
<div class="body">
<p>
Dear customer, to ensure proper use of the site it is necessary to enable cookies.
</p>
</div><!-- /.body -->
</div>
</div>
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.
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"));
}
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')
}
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.
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.
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.
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.
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
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') });
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)
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/
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."); }
I am trying to browse a website, however, it only works under Windows and Mac because they use the navigator.platform from JavaScript to find out the architecture I am running on. Of course, they also use the browser's user agent, but that was easy to spoof.
Here is the .js in question: http://pastebin.com/f56fd608d. The code responsible for browser detection is at the top. Is there any way of changing the .js file before the site runs, or something similar, so I can eliminate the check?
Using the JavaScript console yields:
>navigator.platform
Linux i686
Evidently I changed the browser's user agent, but navigator.platform does not seem to take it's value from the user agent.
Maybe someone knows how to change the value returned by navigator.platform, because I hate running Windows under VirtualBox to use this site.
EDIT:
This could be of interest because Linux users might be artificially denied access to websites, and can do nothing about it.
var fakePlatformGetter = function () {
return "your fake platform";
};
if (Object.defineProperty) {
Object.defineProperty(navigator, "platform", {
get: fakePlatformGetter
});
Object.defineProperty(Navigator.prototype, "platform", {
get: fakePlatformGetter
});
} else if (Object.prototype.__defineGetter__) {
navigator.__defineGetter__("platform", fakePlatformGetter);
Navigator.prototype.__defineGetter__("platform", fakePlatformGetter);
}
Since you can't directly set navigator.platform, you will have to be sneaky - create an object that behaves like navigator, replace its platform, then set navigator to it.
var fake_navigator = {};
for (var i in navigator) {
fake_navigator[i] = navigator[i];
}
fake_navigator.platform = 'MyOS';
navigator = fake_navigator;
If you execute this code before the document loads (using GreaseMonkey, an addon or a Chrome extension), then the page will see navigator.platform as "MyOS".
Note: tested only in Chrome.
Provided that the browser you're using supports Object.defineProperty() (it likely does), a more modern way of achieving the same goal is as follows:
Object.defineProperty(navigator, 'platform', {
value: 'my custom value',
configurable: true // necessary to change value more than once
});
This allows you to set it to any custom value you want, and it also allows you to change it as many times as you want without needing to reload the page.
For a Mozilla-based browser, GreaseSpot / Code Snippets # Hijacking browser properties demonstrates how it may be done. This code may be injected from a GreaseMonkey script.
about:config - > general.platform.override
Attempting to change this property (at any time) in Firefox yields:
Error: setting a property that has only a getter
Source File: index.html
Line: 1
So I think you will have a hard time.
I'd try to contact the author about obtaining a fix.