# Bot Detection Surface Brainstorm — Round 5 Candidates

**Context:** Surveyed every signal we've shipped or validated this session.
Looking for detection surfaces that don't overlap with existing checks and
have a plausible mechanism for distinguishing headless / automation / stealth
from real human use.

**Existing coverage (for orthogonality check):**
- Mouse: coalesced_mouse_synthesis, low_input_complexity (ApEn), input-entropy (vel/dir)
- UA: navigator.webdriver, vendor+platform, worker UA mismatch, emoji/font platform lie
- WASM: SAB disabled, hyphenation engine, WASM/JS speedup
- WebGPU: lockedapart (10 traces fail)
- Phase 1: phase_imaging clock signals, chrome object inspection, plugins/mimeType
- Stealth-only: worker_ua_mismatch, ua_platform_lie, emoji_no_color, platform_ua_inconsistency

---

## 1. SpeechSynthesis voice count (Phase 1) — HIGH PRIORITY

**Surface:** `window.speechSynthesis.getVoices().length`

**Mechanism:** Real Chrome installs TTS voices from the OS (Samantha, Alex,
Microsoft David, etc.). Headless Chromium ships **0 voices** because there's no
TTS engine — it's a build-time dependency. This is a binary discriminator:
real Chrome has 5-50+ voices, headless Chrome has 0.

**Why orthogonal:** None of our existing checks touch Web Speech API. Audio
synthesis fingerprint is independent of canvas/GL/WebGPU.

**False positive risk:** Low. Users on locked-down corporate Windows with no
TTS voices installed would be flagged — but this is rare and detectable via
navigator.platform = "Win32" + missing language packs.

**Spike complexity:** Trivial. 3-line JS check. 1-click verification.

---

## 2. MediaDevices.enumerateDevices labels (Phase 1) — HIGH PRIORITY

**Surface:** `navigator.mediaDevices.enumerateDevices()` then check `device.label`

**Mechanism:** Real cameras and microphones have **device names in their
labels** ("FaceTime HD Camera", "Built-in Microphone", "USB Audio Device").
Headless Chromium returns an array of device entries with **empty string labels**
because no actual hardware enumeration occurred. Even if a stealth framework
spofs `navigator.mediaDevices`, the device label fill rate is hard to fake
without enumerating actual host hardware.

**Why orthogonal:** None of our checks enumerate hardware. Worker UA + emoji
checks are about platform lies; this is about hardware presence.

**False positive risk:** Low. Real Chrome with no camera/mic permissions
returns empty labels too — but that's a permissions state we can detect
separately (label empty + permissions = "prompt" → real but no permission).

**Spike complexity:** Low. Async API, but Promise.all on enumerateDevices works.

---

## 3. Performance.memory.jsHeapSizeLimit (Phase 1) — HIGH PRIORITY

**Surface:** `performance.memory.jsHeapSizeLimit` (Chromium-only)

**Mechanism:** Real Chrome reports `jsHeapSizeLimit` of **2-8 GB** depending
on system RAM and Chrome flags. Headless Chromium without `--max-old-space-size`
tuning reports a **default 1 GB or unusual values** like 2172649472 (exactly
2.03 GB, the JS engine default). Stealth frameworks that bump `navigator.deviceMemory`
forget this one because it's on `performance`, not `navigator`.

**Why orthogonal:** Touches a different namespace (`performance`, not `navigator`).
Most stealth frameworks focus on `navigator.*` overrides.

**False positive risk:** Medium. Real Chrome on low-RAM machines reports 1GB.
Need to combine with `navigator.deviceMemory` cross-check: deviceMemory=8 +
jsHeapSizeLimit=1GB = suspicious (inconsistent).

**Spike complexity:** Trivial. Single property read.

---

## 4. WebGL GPU adapter info — SwiftShader detector (Phase 1) — HIGH PRIORITY

**Surface:** `navigator.gpu.requestAdapter().info`

**Mechanism:** Headless Chromium without `--enable-unsafe-swiftshader` falls
back to **SwiftShader**, a software renderer that reports:
- `vendor: "Google Inc. (Google)"`
- `architecture: ""` or `"unknown"`
- `description: "SwiftShader Device"` (sometimes)

Real GPU reports vendor="NVIDIA Corporation", "AMD", "Intel", "Apple", etc.,
with concrete architecture ("turing", "rdna2", "gen12").

**Why orthogonal:** The existing `webgpu_lockedapart` only checks if WebGPU
is supported. This goes deeper — checks WHICH adapter is exposed.

**False positive risk:** Low. Real GPU strings are well-known. The signal
catches headless WITHOUT GPU; if user has real GPU, vendor is hardware-specific.

**Spike complexity:** Low. Async API, two `await`s.

---

## 5. AudioContext fingerprint (Phase 1) — MEDIUM PRIORITY

**Surface:** Generate a known AudioBuffer, sum its samples, hash the result.

**Mechanism:** Real hardware produces deterministic-but-unique audio sums
based on the audio stack's float precision, denormal handling, and resampling.
Headless Chromium without a real audio stack returns **default values** that
are stable across machines but don't match any real user's profile. The
**sample rate** itself is also a signal: real Chrome returns 44100 or 48000;
headless can return 8000, 22050, or other unusual values depending on flags.

**Why orthogonal:** Existing checks don't probe audio APIs.

**False positive risk:** Medium. Audio fingerprints vary by browser version
and OS audio configuration. Need careful normalization.

**Spike complexity:** Medium. AudioBuffer setup, OfflineAudioContext, sample
summing, hash computation.

---

## 6. Storage.estimate() quota variance (Phase 1) — MEDIUM PRIORITY

**Surface:** `navigator.storage.estimate()` returns `{quota, usage}`

**Mechanism:** Real Chrome reports quotas based on actual disk size (often
100 GB+ on modern machines). Headless Chromium without proper storage
implementation returns **default values** (often 0 or exactly 120 MB, the
StorageManager default). The combination `quota < 1GB AND usage === 0` is
suspicious on a real machine but normal in headless.

**Why orthogonal:** Storage API isn't touched by existing checks.

**False positive risk:** Low for desktop (real users have disk). High for
incognito/private mode (which intentionally limits quota).

**Spike complexity:** Trivial. Async API, single call.

---

## 7. devicePixelRatio + Visual Viewport scale (Phase 1) — MEDIUM PRIORITY

**Surface:** `window.devicePixelRatio` and `window.visualViewport.scale`

**Mechanism:** Real users have devicePixelRatio in {1, 1.25, 1.5, 2, 2.25,
2.5, 3} depending on monitor. Headless Chromium with default viewport
returns exactly **1**. Real users who have zoomed have `visualViewport.scale
≠ 1`. A pixel-perfect `devicePixelRatio === 1 && visualViewport.scale === 1`
combination is suspicious on a returning visitor.

**Why orthogonal:** No existing check touches viewport properties.

**False positive risk:** Medium. First-visit real users with default zoom
have DPR=1 too. Need to combine with `screen.width/height` mismatch checks.

**Spike complexity:** Trivial. Two property reads.

---

## 8. Notification.permission steady-state (Phase 3) — MEDIUM PRIORITY

**Surface:** `Notification.permission` returns `"default" | "granted" | "denied"`

**Mechanism:** Real users who have been prompted (which happens on most
websites via push notifications, social media widgets, etc.) have permission
in `"granted"` or `"denied"` state. Returning visitors with state always at
`"default"` indicates either a fresh browser install or an automation
framework that creates new contexts without preserving state. **Stealth
frameworks spawning new contexts per request always return `"default"`.**

**Why orthogonal:** Behavioral signal based on session persistence, not
hardware/API presence.

**False positive risk:** Medium. New Chrome installs start at "default".
Privacy-focused users may have it blocked at OS level.

**Spike complexity:** Trivial. Single property read. But: needs Phase 3
(site must have prompted the user before, otherwise "default" is normal).

---

## 9. CSS.supports() comprehensive probing (Phase 1) — LOW PRIORITY

**Surface:** Query `CSS.supports()` for 20+ advanced CSS features
(:has(), container queries, subgrid, color-mix(), etc.)

**Mechanism:** Real Chrome supports most modern CSS features. Headless
Chromium with `--disable-features=` flags or older build dates supports fewer.
A feature that "should" be supported but isn't indicates flag stripping.

**Why orthogonal:** None of our checks use CSS.supports().

**False positive risk:** High. Feature support varies by Chrome version,
not just by headless. Need to compare against user's reported Chrome version.

**Spike complexity:** Low. Just iterate a feature list.

---

## 10. Trusted Types policy presence (Phase 1) — LOW PRIORITY

**Surface:** `window.trustedTypes` — read-only PolicyFactory object

**Mechanism:** Sites that use Trusted Types policies (DOMPurify, GitHub,
Twitter) have `window.trustedTypes` populated with named policies. Headless
or stealth frameworks often fail to recreate the policy factory state when
proxying the window. Mismatch between the site's known policies and what
`window.trustedTypes.getPolicyNames()` returns indicates tampering.

**Why orthogonal:** Targets DOM hardening state, not hardware/UA.

**False positive risk:** High. Only useful on sites that USE Trusted Types.
detect-bot doesn't (yet).

**Spike complexity:** Medium. Need to enumerate expected policies per site.

---

## Recommended spike priority

**Build first (Phase 1, all trivial):**
1. **SpeechSynthesis voices** — strongest binary discriminator, 3-line spike
2. **MediaDevices labels** — strong, also tests Permissions integration
3. **Performance.memory** — single property, high-value
4. **WebGL adapter info** — ortho to existing WebGPU check
5. **devicePixelRatio + viewport** — trivial, useful stealth signal

**Build second (Phase 1, more involved):**
6. **AudioContext fingerprint** — needs careful normalization
7. **Storage.estimate()** — simple API, good signal

**Defer (lower value or false-positive risk):**
8. Notification.permission — needs site context to be useful
9. CSS.supports() — high false positive risk
10. Trusted Types — only useful on specific sites

**Honest assessment of orthogonality:**
- #1-#4 are nearly perfectly orthogonal — they touch completely different
  browser subsystems (TTS, hardware enumeration, V8 heap, GPU).
- #5-#7 are moderately orthogonal — they probe APIs we don't currently check.
- #8-#10 are lower-priority — either behavioral (state-dependent) or
  context-dependent.

**Build order recommendation:** spike #1, #3, #4 in parallel — they're all
trivial and high-value. Then #2 (async API, slightly more complex). Then
re-evaluate based on real-world false positive rates from production data.
