Browser-based rogue device correlation engine. Passive-style local network intelligence built entirely on browser APIs — no server, no raw packets, no elevated privileges. Everything runs client-side in a single React + Vite + TypeScript page.
⚠️ Built for authorized use — security research, your own network, lab / CTF environments. The techniques here are heuristic browser side-channels, not a substitute for real packet-level tooling, and they only work against hosts and networks you are permitted to inspect.
Think of driftnet as a privacy self-audit for your own device, running entirely inside a browser tab.
Whenever you're online, your machine has an identity on the network: the local addresses it uses, and the public IP the internet sees you as. driftnet takes attendance of that identity, notes what it saw, and remembers it between visits. The next time you run it, it compares notes — Is this the identity I expect? Did an unexpected public address just show up? Are remote-admin ports suddenly reachable? Did something reappear after a long absence?
When something looks off, it raises a flag and gives it a risk score from calm green to alarming red. Its sharpest trick is VPN leak detection: tell it the exit IP you expect to be using, and it will confirm a clean tunnel — or scream if a second, unexpected public IP or a native IPv6 address slips past it.
The clever part: it does all of this without installing anything, without admin rights, and without a server. It repurposes ordinary browser features — the same plumbing behind video calls, image loading, and graphics — to see the small amount your own connection reveals. That makes it a lightweight, heuristic tool: it makes educated guesses, not guarantees.
The "rogue device correlation engine" tagline sounds bigger than the reality — and the reality is the point.
What it does
- Takes attendance of your own device's network identity — the local addresses your machine hands out, and the public IP the internet sees you as.
- Remembers those across visits and flags what changed: a brand-new identity, an unexpected public IP, remote-admin ports that look reachable, an address that reappeared after a long gap.
- Checks whether your VPN is actually containing you: give it your expected exit (egress) IP and it confirms a clean tunnel — or flags a second unexpected public IP, or a native IPv6 address leaking past the tunnel.
- Runs entirely in a browser tab. Nothing installed, no admin rights, no server, no data leaves your machine.
What it deliberately does NOT do
- It does not scan your LAN or discover other people's devices. A browser can't read the router's client table or ARP-sweep the subnet — that needs privileges the browser denies. This is on purpose: share a network with housemates and driftnet never touches or exposes their gear. It only ever looks at itself.
- It is not antivirus / EDR. It never inspects processes, files, or behavior, so it can't detect malware directly. At most it offers circumstantial hints ("this identity's ports changed," "traffic is exiting somewhere you didn't configure") — not detection.
- It is not a replacement for packet-level tooling. For real network mapping you still want nmap / arp-scan. driftnet is the narrow, privilege-free, self-only complement to those — strongest exactly where nmap is awkward: no install, no root, no touching anyone else's devices.
driftnet correlates a device's identity across scans and flags anomalies using three browser primitives:
- Discovery — WebRTC ICE candidate enumeration. An
RTCPeerConnectionpointed atstun.l.google.com:19302with a throwaway data channel forces the browser to gather ICE candidates. Each candidate leaks a local host IP or the public server-reflexive address, which driftnet harvests — both IPv4 and IPv6 address families (src/lib/webrtc.ts,src/lib/ipClass.ts). - Port profiling — image-load timing side-channel. Raw sockets are
blocked, but the browser will try to load an
<img>from anyhost:port. The image never decodes, so we always hitonerror— but how fast the error fires is a timing oracle for reachability across common ports (80, 443, 8080, 8443, 21, 22) — seesrc/lib/portProbe.ts. - Observer identity — canvas/WebGL fingerprint. A canvas + WebGL renderer
hash identifies the observing machine so ledgers from different analysts can
be told apart (
src/lib/fingerprint.ts).
Each device is scored 0–100 (RiskEngine, src/lib/RiskEngine.ts) from
additive, weighted signals. Public-IP scoring is egress-aware: a public IP
that matches your configured egress (see below) is expected and benign; the
alarm is for unexpected public addresses.
| Signal | Weight | Tier driver |
|---|---|---|
| New device (never seen) | +35 | elevated |
| Reappeared after >7 days absent | +30 | elevated |
| Port profile changed since last seen | +45 | high |
| Link-local address (169.254.0.0/16) | +25 | unusual |
| Public IP matches configured egress (VPN confirmed) | +5 | info |
| Public IP exposed — no egress baseline set | +30 | elevated |
| Public IP differs from configured egress (leak / relay) | +60 | critical |
| Native IPv6 exposed while IPv4 tunneled (v6 leak) | +55 | critical |
| Global IPv6 exposed — no egress baseline set | +35 | elevated |
| Carrier-grade NAT (100.64.0.0/10) | +15 | unusual |
| Remote-admin ports exposed (21 / 22) | +20 | elevated |
Score → tier: LOW (<20), MEDIUM (20–44), HIGH (45–74), CRITICAL (≥75).
driftnet can hold an expected egress so VPN traffic is scored correctly.
Set your expected public IP (IPv4 and/or IPv6), plus optional ASN and a label
(all user-provided; no external lookups). It's persisted to localStorage under
driftnet:egress.
- Public IP matches the baseline →
VPN egress confirmed(informational). - Public IP present with no baseline → moderate "exposed" flag.
- Public IP differs from the baseline, or a global IPv6 leaks while IPv4 is
tunneled →
CRITICAL.
Leave it unset and driftnet still works — it just can't tell "my VPN, working" from "an address I didn't expect," so it errs toward flagging.
Every discovered identity is persisted to localStorage under the key
driftnet:devices as a keyed store. Each record holds: ip, ipClass,
ipVersion (4 | 6), firstSeen, lastSeen, seenCount, portProfile
(port + latency results), portSignature, fingerprintHash, rttMs, and the
computed riskScore / riskLevel / riskSignals. Records persisted before
ipVersion existed are migrated on load and treated as IPv4.
A dark, monospace, three-panel terminal layout:
- Device ledger — sortable table of all known identities (by risk, first/last seen, host), with v4/v6 entries visually distinguished.
- Detail pane — selected identity, history, signal breakdown, port profile, address family, and whether the public IP matched the configured egress.
- Scan log — live output during enumeration.
Plus a rescan button, an egress-baseline editor, clear log, and clear ledger action.
src/
hooks/
useDeviceLedger.ts localStorage persistence + merge bookkeeping
useNetworkScanner.ts WebRTC discovery + port-probe orchestration
useEgressConfig.ts expected-egress baseline persistence
lib/
RiskEngine.ts stateless, egress-aware 0–100 risk scoring
webrtc.ts ICE candidate enumeration (IPv4 + IPv6)
portProbe.ts image-load timing port oracle
fingerprint.ts canvas/WebGL observer identity
ipClass.ts IPv4 + IPv6 range classification
format.ts timestamp / risk formatting helpers
components/
DeviceLedger.tsx DetailPane.tsx ScanLog.tsx EgressPanel.tsx
App.tsx main.tsx types.ts styles.css
npm install
npm run dev # vite dev server on :5173
npm run build # typecheck + production build
npm run lint # tsc --noEmit
npm test # vitest — RiskEngine + ipClass unit coverage- Modern browsers increasingly return mDNS (
*.local) ICE candidates that hide the raw IP; those are surfaced but not probed. - The port oracle is a heuristic — a fast
onerrormeans reachable (which includes an active refusal), not necessarily open. HTTPS upgrades, HSTS, and proxies all add noise. Treat results as signal, not ground truth. - The egress baseline is user-provided — driftnet doesn't verify your claimed exit IP against any external source (by design; zero backend).
- No backend means no cross-device sync; the ledger lives in one browser.