
Browser privacy-leak detector — eight detection modules, risk scoring, and per-account history, all in your browser.
Browser privacy-leak detector — eight detection modules, risk scoring, and per-account history, all in your browser.
Built with Verdent — an agentic coding assistant. Every detection module, the analytics dashboard, the API-key auth flow, the bilingual UI and the Kubernetes-ready Docker image were designed and implemented in pair-programming sessions driven by Verdent.
Open a URL, click Run all, and within a few seconds Leakish tells you what
your browser is leaking about you and your network — over WebRTC, fingerprinting
APIs, DNS, and a server-side probe. Eight independent modules each produce a
typed result + risk signals, an aggregated score (0–100) and a level
(safe / warning / critical).
Signed-in users can save scans to MySQL, browse history, and open an analytics
dashboard that shows distribution charts, fingerprint-hash repetition, IP / ASN
timeline, outlier scans, and a pairwise compare. Everything else (the detector
itself) works without an account — anyone can land on /app and run a scan.
Each module is registered through a single typed DetectionModule<T> interface
and runs in isolation — one module throwing does not affect the others. Enable
state per module is persisted in localStorage with a version key, survives
refresh, and the Run all button only walks the enabled set.
/api/detect/network to read its own server-observed
IP, or (c) the DNS module talks to https://dns.google/resolve.leakish.locale cookie, drives <html lang>, page metadata, every screen,
every API error response, and the magic-link sign-in email.det_… API keys on the
Integrations page (SHA-256 stored, plaintext shown once). Every
/api/detect/* endpoint accepts either a session cookie or a Bearer key, so
the same scan can be saved from a CLI / CI job./scans/analytics — distribution charts across
timezone / platform / language / screen / WebGL / ASN / country / font count,
fingerprint-hash repetition with a per-kind risk grade, IP / ASN timeline,
multi-axis outlier detector, pairwise compare with 0–100 similarity score.lib/risk/assess.ts produces
typed s with stable IDs (e.g. ), per-module
summaries, and a single aggregate score. Locale-aware via injection.Prereqs: Node 22+ (anything 20+ works for local dev), Yarn 1.x, a running MySQL instance, and (optionally) a Google OAuth client.
yarn install
cp env.example .env.local # fill in DATABASE_URL, NEXTAUTH_SECRET, IP_HASH_SALT at minimum
yarn prisma:generate
yarn db:push # provision tables on a fresh DB
yarn dev # http://localhost:3000
Minimum env to get a working sign-in:
DATABASE_URL — e.g. mysql://root:[email protected]:3306/detectNEXTAUTH_SECRET — openssl rand -base64 32NEXTAUTH_URL — http://localhost:3000 in devIP_HASH_SALT — openssl rand -base64 32Optional:
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET for Google sign-inMAILGUN_API_KEY / MAILGUN_DOMAIN / EMAIL_FROM for real magic-link delivery
(without these the link is printed to the dev server console)IPINFO_TOKEN to enrich the network probe with ASN / VPN / proxy dataSee env.example for the full annotated list.
app/
├── (marketing)/ # / — Landing (server-resolved auth state + client UI)
├── app/ # /app — the detector page
├── login/ # /login
├── scans/ # /scans, /scans/[id], /scans/analytics
├── integrations/ # /integrations — API key management
├── api/
│ ├── auth/ # NextAuth route + apiKey-login + tests
│ ├── detect/
│ │ ├── analytics/ # aggregate + pairwise-diff
│ │ ├── network/ # server-side egress probe
│ │ ├── scans/ # save / list / get / delete
│ │ └── uniqueness/ # how many users share this fingerprint hash
│ ├── integration/keys # create / revoke / list API keys
│ └── health/ # liveness probe for k8s
├── opengraph-image.tsx # dynamic OG (per-locale)
├── apple-icon.tsx # dynamic apple-touch icon
├── icon.svg # shield gradient
└── layout.tsx # metadata + theme-init script + LocaleProvider
components/
├── modular-detector.tsx # top-level shell (header + module list + result/overview)
├── detector/ # module-list, result-panel, category filter, state hook
├── result-details/ # one detail view per module (typed switch)
├── overview/ # overview panel + diagnostics export
├── auth/ # AuthGateProvider, login dialog, login form
├── providers/ # SessionProvider wrapper
└── ui/ # shadcn-style primitives + theme/locale toggles
lib/
├── detection-modules/ # 8 module impls + ModuleManager + IP utils + tests
├── risk/ # assess.ts — RiskSignal/score aggregation
├── i18n/ # en.ts / zh.ts + Messages type + locale client/server
├── api/ # withDetectAuth wrapper, scan-payload Zod schema
├── client/ # browser-side scan-api (extractFingerprintInputs, saveScan, lookupUniqueness)
├── server/ # network-probe, IP-hash salt, fingerprint hashing, rate-limit, send-email, analytics
├── persistence/ # SSR-safe localStorage wrappers
├── env.ts # runtime env validation
├── logger.ts # dev-only diagnostic logger
└── prisma.ts # singleton client targeted at .prisma/detect-client
prisma/schema.prisma # User / Account / Session / DetectScan / ApiKey / DetectFingerprintHash
/app. The detector iterates enabled modules
through ModuleManager.runAllEnabledModules — each module's failure is
caught and isolated so other results still surface.DetectionResult<T> (with success | failure
tag and module-specific data). The result panel renders a per-module
detail component via a type-safe switch.lib/risk/assess.ts walks each result, emits RiskSignals with stable IDs,
and produces an OverallAssessment (score + level + counts).POSTs
/api/detect/scans. The server validates with Zod, runs its own network
probe to record the server-observed IP / ASN / VPN flags, and writes a
DetectScan row plus rows.Unit tests live alongside their subjects under __tests__/ directories:
yarn test # full suite
yarn test path/to/file # single file
yarn test -t "name pattern" # by test-name regex
Highlights:
lib/detection-modules/__tests__/ — IP classification + mDNS extraction,
module-manager registration & failure isolation, mocked RTCPeerConnection
for the WebRTC module, jsdom-shape tests for the browser-fingerprint module,
CDP/automation signal generation.lib/server/__tests__/analytics.test.ts — distribution bucketing, repetition
grouping, outlier scoring, pairwise similarity math.app/api/detect/scans/__tests__ — POST save (validation, rate limit,
idempotency, pruning) + GET list + DELETE single.app/api/integration/keys/__tests__ — create / revoke / list with mode
gating (API-key sessions cannot mint other API keys).app/api/auth/__tests__ — apiKey-login route (CSRF, format check, revoked /
expired handling, last-used touch).Lint + typecheck:
yarn lint
npx tsc --noEmit
The included Dockerfile produces a Next.js standalone image on
node:22-alpine. Production env is injected via Kubernetes Secret — placeholder
values are baked at build time so SKIP_ENV_VALIDATION is not required at
runtime. A prisma db push Job runs the schema before the rollout. See
.github/workflows/deploy-tke.yml for the full image-build → migrate → rollout
pipeline.
This open-source build has no paywall. Saves are governed solely by the
in-process per-user rate limiter in lib/server/rate-limit.ts (10 saves/hour),
and history retention is unbounded.
See LICENSE.
| ID | Module | What it inspects |
|---|
network-probe | Network egress probe | Server-observed public IP, ASN, country/region/city, VPN/proxy/Tor/hosting flags, TLS/HTTP |
webrtc | WebRTC | Public IPv4 + IPv6 via STUN, RFC1918 leaks, mDNS-obfuscated .local candidates |
browser-fingerprint | Browser fingerprint | UA, screen, timezone, plugins, CPU cores, device memory, language list, DNT, cookies |
canvas-fingerprint | Canvas / WebGL | 2D + WebGL renderer/vendor strings, text-rendering quirks, supported image formats |
audio-fingerprint | Audio | AudioContext sample rate, channels, latency, oscillator output hash |
font-detection | Fonts | 40+ named-font probes via Canvas width-difference detection |
dns | DNS | DoH reachability, DNSSEC signal, resolver geolocation, DNS-leak heuristics |
cdp-detection | Automation / headless | navigator.webdriver, CDP timing, Chromium-only API surface, port probes — confidence 0–1 |
RiskSignalwebrtc.public-ip-leakMessagesPOST /api/detect/scans accepts an
Idempotency-Key header, caps each user at 200 scans, and rate-limits saves
to 10/hour.node:22-alpine,
Next.js standalone output, deploys to Kubernetes via the workflow under
.github/workflows/.| Path | Purpose |
|---|
/ | Marketing landing (aurora hero, feature cards, FAQ, CTA) |
/app | The detector — module list + result panel + overview |
/login | Standalone sign-in page (Google / email / API key) |
/scans | Signed-in user's saved-scan history |
/scans/[id] | Single saved-scan detail |
/scans/analytics | Distribution / repetition / timeline / outliers / compare |
/integrations | Manage det_… API keys (create / revoke / how-to) |
DetectFingerprintHash/scans/[id], contributes to the analytics
aggregates, and counts in the uniqueness lookup.| Layer | Pieces |
|---|
| Framework | Next.js 14 (App Router, force-dynamic everywhere), React 18 |
| Language | TypeScript 5, strict mode |
| UI | Tailwind CSS 3, shadcn/ui primitives on Radix, Lucide icons, Framer Motion |
| Auth | NextAuth 4 (Google OAuth + magic-link email) + Prisma adapter |
| Data | Prisma 6 → MySQL (custom client output at .prisma/detect-client) |
| Validation | Zod |
| Tests | Jest 29 + ts-jest + jsdom |
| Build / deploy | Multi-stage Dockerfile, Next.js standalone, Kubernetes manifests |