How private mode leaks
Private mode isolates and shrinks the browser's storage, and that is exactly what gives it away. The classic Chrome tell was window.webkitRequestFileSystem, an API that returned an error in incognito but worked normally otherwise - a single function call was enough to tell the difference. Google closed that gap, so detection moved to the storage quota: navigator.storage.estimate() (an API that tells a page how much space it may use) reports a much smaller quota in private mode - capped to a small fraction of disk, or to a fixed ceiling - than a normal session would on the same machine. Compare that reported quota against how big the device looks, and the mode shows through.
Other differences have been used over the years: IndexedDB (the browser's built-in database) throwing errors or acting differently, the Quota API's temporary-versus-persistent storage limits, and service-worker or cache data not surviving the session. Each browser release tends to patch whichever tell is currently popular, so the exact probe in use keeps shifting - but the underlying fact, that private mode constrains storage, keeps producing new ones.
Why sites detect it at all
There are two motivations. Risk scoring: abusive and automated traffic is disproportionately private, because scrapers and fraud tools default to fresh, stateless sessions - no saved cookies or history - to avoid carrying identifiers between visits. Incognito alone is not proof of a bot, since plenty of real people browse privately, so it is used as a soft signal folded into a broader risk score, not a standalone block.
The other motivation is business logic: metered paywalls (news sites) and free-trial limits historically used incognito detection to stop readers from resetting their free-article count by opening a private window. This is the same plumbing the anti-bot use case relies on, which is why incognito detection turns up both in paywall scripts and in fraud/bot stacks.
Implications for scrapers
For scraping, the practical lesson is that running headless does not put you in incognito by default - a headless browser with a normal user-data directory (the on-disk folder that holds a browser profile) reports normal storage. But several stealth setups do use private contexts or throwaway profiles, and if the site scores incognito as risk, that choice is working against you. If a target gates on private mode, the fix is to run with a persistent profile and real storage quota so navigator.storage.estimate() looks like an ordinary install.
As always, coherence matters more than any single value: the storage quota should match the kind of device you are claiming to be, and the persistence behaviour should look like a returning user if you are presenting cookies and history. Treat incognito detection as one more consistency probe in the stack rather than a special case.
