# Nothing Private: a browser fingerprinting proof of concept that survives incognito mode

> Nothing Private is a GPL-3.0 proof-of-concept web application that demonstrates how websites can identify and track visitors even in private browsing or incognito mode. It uses Client.js to extract browser fingerprints, stores them against a submitted name in a MySQL database, and retrieves the name on the next visit.

**gautamkrishnar/nothing-private** — Do you think you are safe using private browsing or incognito mode?. :smile: :imp: This will prove that you're wrong.   Previously hosted at nothingprivate.ml

- Repository: https://github.com/gautamkrishnar/nothing-private
- Website: https://nothingprivate.gkr.pw
- Stars: 2,291 · Forks: 166
- Language: JavaScript
- License: GPL-3.0
- Published: 2026-10-11 · Updated: 2026-10-11 · Language: en
- Canonical page: https://hysenlabs.com/projects/gautamkrishnar-nothing-private

## Incognito mode clears history but does not change the browser's fingerprint

Nothing Private addresses a specific misconception: that private browsing or incognito mode prevents websites from identifying who you are. Clearing history and suppressing cookies is what incognito mode does. What it does not do is change the set of hardware and software signals that the browser leaks on every page load, and those signals, when combined, can form a fingerprint that survives the session boundary.

This proof of concept makes that visible without any theoretical explanation. You visit the website, type a name into the form and click a button. Then you open a new private browsing window, visit the same URL without entering anything, and see the name you submitted in the previous session. The mechanism is browser fingerprinting, not cookies, not local storage, and not any of the storage mechanisms that private browsing clears.

The project targets developers and educators who want a demonstration they can point someone to, or who want to understand the technique by reading the source code. It is hosted at nothingprivate.gkr.pw and can be self-hosted with Docker.

## Client.js reads 16 data points to produce a fingerprint stored in MySQL against a name

The fingerprinting mechanism depends on Client.js, a JavaScript library that reads browser attributes without requesting permissions. When you submit the form, the frontend calls Client.js to gather 16 data points from your browser:

```text
user agent, screen print, color depth, current resolution, available resolution, device XDPI, device YDPI, plugin list,
font list, local storage, session storage, timezone, language, system language, cookies, canvas print
```

These 16 values are combined into a single fingerprint string. That fingerprint travels to the PHP backend alongside the name you entered. The backend stores both in a MySQL database. On your next visit, the frontend again calls Client.js to collect the same 16 data points and sends the resulting fingerprint to the backend. The backend queries the database for a matching fingerprint and, if one exists, returns the associated name.

No cookies are set. No session tokens are stored. The entire identification mechanism depends on the stability of your hardware and software configuration across visits. If any of the 16 data points changes between visits, such as a font list change after a software update, the fingerprint changes and the match fails.

The server files live in the db_server/ directory. The README links to a historical SQLite version of the backend at a specific commit (2abc011c39e500279169f70118048d6592860cce), which means the project once used SQLite before switching to MySQL.

## Running locally with Docker requires 3 commands and no separate setup

Self-hosting the demo requires Docker. The README gives 3 commands:

```bash
git clone git@github.com:gautamkrishnar/nothing-private.git
cd nothing-private
docker-compose up -d
```

After the containers start, visit http://localhost/ in your browser. The comment in the README notes that adding --build when running after a git pull forces a rebuild of the image.

The docker-compose.yml defines 2 services: app and database. The app service builds from the Dockerfile and maps port 80 on the container to port 80 on the host. The database service uses the mysql:8.0 image with the mysql_native_password authentication plugin enabled via the command line. The db_server/ directory is mounted to /docker-entrypoint-initdb.d inside the MySQL container, which means the schema initialization scripts in that directory run automatically when the container starts for the first time.

For front-end development without Docker, the package.json defines a start script that launches Vite on port 8080. The test script confirms the server is up at http://localhost:8080/index.html before running Cypress integration tests. Karma and Jasmine handle unit tests.

## The docker-compose.yml uses identical placeholder credentials for every MySQL field

A close reading of docker-compose.yml reveals that the MySQL service is configured with the same string, safeuser, for MYSQL_USER, MYSQL_PASSWORD, MYSQL_ROOT_PASSWORD and MYSQL_DB, with safedb as the database name. Every credential field uses a placeholder value, which is fine for a local demo but matters if anyone runs the same file on a publicly accessible host.

The base image for the app container is specified at the top of the Dockerfile:

```dockerfile
FROM php:7.4-apache
```

This image variant has passed its support cycle and no longer receives security updates. Vulnerabilities discovered after the support window closed are not patched in this image, so any service built on it is exposed to those findings. For a project intended purely as a local educational demonstration where the container runs only on the developer's machine, the choice is understandable and the risk is limited. For anyone who wants to run a public-facing instance, the base image needs updating and all credentials need replacing before the container is exposed.

Two shell scripts run as part of the container build: install_composer.sh and make_docker.sh, both located in the docker/ directory. What those scripts do is not documented in the top-level README, so reading them directly is necessary before deploying to any non-local environment.

## Canvas print and font list are the most persistent fingerprint signals in the 16

Among the 16 data points Client.js collects, canvas print and font list are notable for their persistence. A canvas fingerprint works by drawing an invisible image in the browser and reading back the pixel values, which differ slightly between GPU drivers, operating system rendering engines and font rendering settings. Those pixel differences create a string that tends to be stable across browser sessions and browsing modes.

Font list fingerprinting reads the fonts available on the operating system. The list changes only when fonts are installed or removed, which is infrequent on most machines. Together, canvas print and font list contribute to a fingerprint that is harder to spoof than user agent strings, which are easy to override.

The project links to panopticlick.eff.org and amiunique.org as external references where readers can check their own browser's uniqueness. Both sites run real fingerprinting checks. The README also links to a FreeCodeCamp article on web tracking and references private browsing myths at privatebrowsingmyths.com, though none of these links are part of the project code itself.

## The browsers-with-countermeasures/ directory lists browsers that resist the fingerprint

The repository holds a browsers-with-countermeasures/ subdirectory with its own README. The top-level README links to it as a community-curated list of browsers that implement countermeasures against browser fingerprinting. The content of that list is not reproduced in the top-level README, so checking its current state requires opening the subdirectory.

Known countermeasures against canvas fingerprinting include adding noise to canvas output, blocking the canvas API, or reporting a generic value. Browsers that block plugin list and font list enumeration further narrow the number of distinctive data points available. Whether these countermeasures defeat the specific combination of 16 signals that Nothing Private uses depends on which of the 16 are spoofed or blocked, and the project provides the running demo as the test rather than a documented report.

For developers building a fingerprinting-resistant browser or testing a privacy extension, running the demo and checking whether the name is returned on the second visit is a practical self-test. If the name is not returned, the combination of countermeasures active in that browser is sufficient to defeat this particular fingerprint.

## Package.json lists version 1.0.0 while the most recent formal build was 2.8 from 2020

Version 2.8 was published on 2020-05-10 as the most recent build in the GitHub build list, while the package.json at the repository root carries version "1.0.0". That field was never bumped to match the build numbers in the repository, and the inconsistency means any tooling that reads the npm version field to determine the current state of the code will return "1.0.0" regardless of which build is present.

A push arrived on 2026-10-02, confirming the repository remains active. What changed in that push is not documented in the top-level README, and no entry in the build list corresponds to 2026-10-02. Bridging the gap between the 2.8 build and the current working tree requires reading the git history directly, since the README does not describe what has changed in the code since the last formal build.

For an educational demonstration, the version inconsistency is a minor concern. For anyone building on top of Nothing Private or integrating it into a curriculum with pinned versions, checking the git history for what has changed since 2020 before using the current code is the safer path.

## Conclusion

Nothing Private is worth running for developers or educators who want a concrete, running illustration of how browser fingerprinting bypasses incognito mode. It is not suitable for production use as a tracking system: the default docker-compose.yml uses identical placeholder credentials for every MySQL field, and the code targets PHP 7.4. Before running it publicly, change all credentials in docker-compose.yml, review the db_server/ directory for schema and security settings, and test against the browsers-with-countermeasures/ list to see which ones defeat the current fingerprint.

## FAQ

### What is Nothing Private?

Nothing Private is a proof-of-concept web application that shows websites can identify and track visitors even in private browsing or incognito mode. It collects a browser fingerprint using Client.js and matches it against a stored name in a MySQL database.

### How does Nothing Private work?

Nothing Private uses Client.js to extract 16 data points from the browser, including canvas print, font list, user agent and timezone. These form a fingerprint that is stored in MySQL alongside a submitted name. On return visits, the same fingerprint is collected and matched to retrieve the stored name.

### How do I run Nothing Private locally?

Clone the repository, enter the directory and run docker-compose up -d. Then visit http://localhost/ in your browser. The docker-compose.yml starts a PHP application on port 80 and a MySQL 8.0 database.

### Does private browsing protect against browser fingerprinting?

According to the README, private browsing only clears browsing history. Your internet service provider, search engines and websites can still track you via fingerprinting techniques. The project links to a list of browsers that implement countermeasures against fingerprinting in the browsers-with-countermeasures/ directory.

### Is Nothing Private safe to deploy publicly?

The default docker-compose.yml uses identical placeholder credentials for all MySQL fields, and the base Dockerfile image is php:7.4-apache, which is past end of life. The project is intended as a local educational demo; deploying it publicly requires updating credentials and the base image.

## Sources

- [gautamkrishnar/nothing-private on GitHub](https://github.com/gautamkrishnar/nothing-private)
- [License: GPL-3.0](https://github.com/gautamkrishnar/nothing-private/blob/master/LICENSE)
- [Project website](https://nothingprivate.gkr.pw)
- [README](https://github.com/gautamkrishnar/nothing-private/blob/master/README.md)
- [Releases](https://github.com/gautamkrishnar/nothing-private/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/gautamkrishnar-nothing-private
