> ## Documentation Index
> Fetch the complete documentation index at: https://vietbui.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Web Fingerprinting

> Web fingerprinting is the process of identifying a browser, device, or automated bot by combining signals from multiple independent layers of a request. Mode…

# Web Fingerprinting

Web fingerprinting is the process of identifying a browser, device, or automated bot by combining signals from multiple independent layers of a request. Modern anti-bot systems (Cloudflare, PerimeterX, DataDome, etc.) score all layers simultaneously — passing one is not enough.

**The Golden Rule:** Every layer must tell the same consistent story. One mismatch = detection.

## The Three Layers

### Layer 1: Network-Level (pre-JS)

Signals extracted before any browser JavaScript runs, at the TCP/TLS connection:

**TCP fingerprint** (kernel-set, cannot be changed by proxies or browsers):

* Initial window size, MSS, TCP options order, TTL
* Per OS: Windows 10/11=window 65535/TTL 128/options MSS+NOP+WS+NOP+NOP+SACK\_PERM; Linux=29200/64/MSS+SACK\_PERM+TS+NOP+WS; macOS=65535/64
* Detected with tools like p0f, Nmap OS detection
* HTTP and SOCKS proxies operate above TCP layer — they cannot modify TCP handshake characteristics; real OS always exposed to network observers

**TLS fingerprint (JA3/JA4)**:

* Cipher suite list, TLS extensions, their order, ALPN protocols in the Client Hello
* JA3: MD5 hash of selected TLS fields; JA4: improved successor
* Set by the HTTP client library or browser's TLS stack
* `curl-cffi` mimics Chrome's TLS fingerprint from Python without a real browser

**HTTP/2 fingerprint**:

* SETTINGS frame parameter order, initial window size, HPACK header order
* Each browser/version has a characteristic pattern

### Layer 2: Browser-Level (JS APIs)

Readable via JavaScript after the connection is accepted:

| Signal                | What it reveals                                              |
| --------------------- | ------------------------------------------------------------ |
| `navigator.webdriver` | `true` for WebDriver-controlled browsers; automation flag    |
| Canvas fingerprint    | GPU/driver-specific pixel rendering for identical draw calls |
| WebGL vendor string   | GPU manufacturer and driver version                          |
| Audio API output      | OS/hardware-specific floating-point differences              |
| Font enumeration      | Installed fonts reveal OS and locale                         |
| Navigator object      | plugins, languages, screen dimensions, platform              |
| Header consistency    | Accept-Language matching navigator.language                  |

`playwright-stealth` / `playwright-extra` patch these JS properties. CDP-native tools (Pydoll) avoid `navigator.webdriver` entirely.

## Proxy Layer Positioning

| OSI Layer       | Protocol    | Proxy Type  | TCP Fingerprint Visible?                                           |
| --------------- | ----------- | ----------- | ------------------------------------------------------------------ |
| 7 (Application) | HTTP, HTTPS | HTTP proxy  | Yes — full content visible; can read/modify headers, cookies, body |
| 5 (Session)     | —           | SOCKS proxy | Yes — protocol-agnostic; cannot inspect content; HTTPS end-to-end  |
| 4 (Transport)   | TCP/UDP     | —           | Always — below all proxies                                         |

Most proxies only handle TCP. UDP traffic (WebRTC, DNS, QUIC/HTTP3) bypasses proxy configuration entirely. Use `--disable-quic` Chrome flag to force HTTP/2 over TCP for QUIC mitigation.

## WebRTC IP Leak

The most common cause of IP leakage in proxied automation. WebRTC uses STUN servers over UDP to discover the real public IP — this happens below the browser's proxy layer. JavaScript on the page can trigger discovery with \~10 lines via `RTCPeerConnection` and Google STUN servers.

Mitigation (Pydoll API):

```python theme={null}
options.webrtc_leak_protection = True  # force WebRTC through proxy only
# or nuclear: options.add_argument('--disable-features=WebRTC')
```

### Layer 3: Behavioral

ML models trained on billions of human interaction events:

* **Mouse**: trajectory curvature, velocity profile, Fitts's Law compliance (larger targets = shorter movement time)
* **Keystrokes**: dwell time (key-down to key-up), flight time (between keys), bigram patterns
* **Scroll**: momentum, inertia, deceleration curves — human scroll has physical realism
* **Event ordering**: `mousemove → mouseover → mouseenter → click` is natural; bots often fire `click` directly

This layer is the hardest to defeat because it requires replicating biomechanical patterns. Even correct network and browser fingerprints can be undone by robotic click timing.

## Detection Is Holistic, Not Per-Layer

A request with:

* ✓ Correct TCP fingerprint (macOS)
* ✓ Correct JA3 (Chrome 120)
* ✗ `navigator.webdriver = true`

...will still be blocked. The system scores all layers and a single high-confidence signal is sufficient.

Conversely, no layer alone is a silver bullet: disabling `navigator.webdriver` does not help if your TLS fingerprint says Python `requests`.

## Evasion Principles

1. **Consistency over perfection**: a correctly configured Firefox fingerprint beats an "almost-right" Chrome fingerprint with one mismatch
2. **Holistic approach**: align network, browser, and behavioral layers together
3. **Use a real browser**: headless Chromium with CDP is better than `requests`; non-headless is better than headless
4. **Residential/mobile proxies**: fix IP reputation and help match expected TLS from those ISPs
5. **Continuous adaptation**: fingerprinting evolves monthly; static evasion setups degrade

## Cloudflare Tiers

**Standard Cloudflare (no Turnstile):** Two flags defeat detection on most sites:

```js theme={null}
{ headless: false, args: ["--disable-blink-features=AutomationControlled", "--window-size=1920,1080"] }
```

`headless: false` forces a real visible browser process; `--disable-blink-features=AutomationControlled` removes the `navigator.webdriver` JS property.

**Cloudflare Turnstile:** Analyzes mouse trajectories, behavioral patterns, advanced fingerprinting. Cannot be passed programmatically. Workarounds: `puppeteer-real-browser` (community library, reported to solve Turnstile in some cases); Chrome Debug Port + MCP (attach to real user-profile Chrome instance with existing auth cookies — zero automation flags set).

## Free vs. Hybrid Stack

| Aspect       | Free/Open-Source                  | Hybrid (OSS + paid proxies) |
| ------------ | --------------------------------- | --------------------------- |
| Success rate | 50–75%                            | 90–99%                      |
| Scale        | 10–100 req/hr                     | 1k–10k+/hr                  |
| Cost         | \$0 + time                        | \$50–300/mo                 |
| Sites        | Simple/static OK; fails protected | All sites including Amazon  |

**Free stack**: Playwright/Puppeteer (headless=false), playwright-stealth, free proxy lists, rotated user-agents, random delays 2–10s, human-like mouse/scroll.
**Hybrid recommended**: Playwright + playwright-stealth + mobile/4G proxies (Proxies.sx, VoidMob, Bright Data) + 2Captcha for CAPTCHA fallback.

Takeaway: free-only suits learning and prototypes. Paid proxies are the minimum addition for production reliability on anti-bot-protected sites.

## Alternative Stealth Tools

* **curl-cffi** (Python) — mimics real Chrome TLS fingerprints; effective against basic detection without a headless browser
* **Camoufox** — stealth-optimized Firefox build
* **playwright-extra / Patchright** — extended stealth patches for Playwright
* **nodriver / undetected-chromedriver** — community alternatives; results vary by target site
* **FlareSolverr** — proxy service that solves Cloudflare challenges

## Legal and Ethical Framework

| Region | Key Law           | Constraint                                                           |
| ------ | ----------------- | -------------------------------------------------------------------- |
| EU     | GDPR              | IP addresses are personal data; lawful basis required for collection |
| USA    | CFAA, state laws  | Circumventing access controls may violate computer fraud law         |
| China  | Cybersecurity Law | Only approved VPN/proxy services permitted                           |

**hiQ v. LinkedIn (2022):** Scraping publicly available data generally permitted; circumventing technological barriers may still violate CFAA.
**QVC v. Resultly (2020):** Excessive requests constitute trespass to chattels — volume and server impact matter, not just technical access.

Ethical minimum: respect `robots.txt`; rate-limit (1+ second minimum between requests, ≤5 concurrent per site); collect only what you need.

High-risk targets to avoid: banking/financial (fraud detection), government portals (legal penalties), healthcare (HIPAA), e-commerce account creation (permanent bans).

## Practical Tool Map

| Layer         | Problem                          | Tool                                      |
| ------------- | -------------------------------- | ----------------------------------------- |
| Network/TLS   | Python HTTP client has wrong JA3 | `curl-cffi`                               |
| Network/TLS   | Headless browser has wrong TLS   | Residential proxy with correct TLS stack  |
| Browser       | `navigator.webdriver` exposed    | `playwright-stealth`, Pydoll (CDP-native) |
| Browser       | Canvas/WebGL artifacts           | `playwright-stealth`, CDP overrides       |
| Behavioral    | Robotic click timing             | Random delays, human-like mouse paths     |
| IP reputation | Datacenter IP flagged            | Residential or mobile proxy               |

## Related Pages

* [Proxy Rotation](/concepts/proxy-rotation) — proxy types and their effect on network fingerprint
* [WebRTC IP Leak](/concepts/webrtc-ip-leak) — UDP-level bypass that defeats otherwise-correct proxy setup
* [Pydoll](/entities/pydoll) — library with systematic fingerprint evasion support

<iframe
  srcDoc="<!doctype html><html><head><meta charset=&#x22;utf-8&#x22;><style>
html,body{margin:0;height:100%;background:#0f1117;overflow:hidden;font-family:ui-sans-serif,system-ui,-apple-system,sans-serif}
#g{width:100%;height:100%}
#hd{position:absolute;top:0;left:0;right:30px;height:22px;display:flex;align-items:center;gap:6px;padding:0 10px;color:#aeb3c2;font-size:10px;letter-spacing:.08em;text-transform:uppercase;z-index:6;cursor:move;user-select:none;touch-action:none;background:linear-gradient(#0f1117cc,#0f111700)}
#gear{position:absolute;top:5px;right:7px;z-index:7;cursor:pointer;color:#aeb3c2;background:#1b1e27;border:1px solid #2b2f3a;border-radius:6px;width:22px;height:22px;display:flex;align-items:center;justify-content:center;font-size:12px;user-select:none}
#panel{position:absolute;top:31px;right:7px;z-index:7;background:rgba(22,25,34,.96);border:1px solid #2b2f3a;border-radius:8px;padding:6px 9px 9px;display:none;width:150px;color:#c9cdd8;font-size:10px}
#panel.open{display:block}
#panel label{display:flex;justify-content:space-between;margin:7px 0 1px;color:#9aa0b0}
#panel input[type=range]{width:100%;margin:0}
#panel .row{display:flex;align-items:center;gap:6px;margin-top:8px;color:#c9cdd8}
</style><script src=&#x22;https://cdn.jsdelivr.net/npm/force-graph@1.51.4/dist/force-graph.min.js&#x22; integrity=&#x22;sha384-Hm6GpQcTNI5VqGgGS7lLxTGtEFcxu/kOVV0B7ozIZRu9blWVvigv5httJQZ2qZmY&#x22; crossorigin=&#x22;anonymous&#x22;></script></head>
<body><div id=&#x22;hd&#x22;>Graph</div><div id=&#x22;gear&#x22;>⚙</div>
<div id=&#x22;panel&#x22;>
<label>Node size<span id=&#x22;vns&#x22;></span></label><input id=&#x22;ns&#x22; type=&#x22;range&#x22; min=&#x22;0.6&#x22; max=&#x22;6&#x22; step=&#x22;0.2&#x22;>
<label>Link width<span id=&#x22;vlw&#x22;></span></label><input id=&#x22;lw&#x22; type=&#x22;range&#x22; min=&#x22;0&#x22; max=&#x22;3&#x22; step=&#x22;0.1&#x22;>
<label>Label size<span id=&#x22;vts&#x22;></span></label><input id=&#x22;ts&#x22; type=&#x22;range&#x22; min=&#x22;0&#x22; max=&#x22;8&#x22; step=&#x22;0.5&#x22;>
<label>Label opacity<span id=&#x22;vto&#x22;></span></label><input id=&#x22;to&#x22; type=&#x22;range&#x22; min=&#x22;0&#x22; max=&#x22;1&#x22; step=&#x22;0.05&#x22;>
<div id=&#x22;depthRow&#x22;><label>Depth<span id=&#x22;vd&#x22;></span></label><input id=&#x22;dp&#x22; type=&#x22;range&#x22; min=&#x22;1&#x22; max=&#x22;5&#x22; step=&#x22;1&#x22;></div>
<div class=&#x22;row&#x22;><input id=&#x22;ar&#x22; type=&#x22;checkbox&#x22;><span>Directional arrows</span></div>
</div>
<div id=&#x22;g&#x22;></div>
<script>
const NODES=[{&#x22;id&#x22;:&#x22;concepts/web-fingerprinting&#x22;,&#x22;label&#x22;:&#x22;Web Fingerprinting&#x22;,&#x22;group&#x22;:&#x22;concepts&#x22;,&#x22;val&#x22;:2.732050807568877},{&#x22;id&#x22;:&#x22;concepts/proxy-rotation&#x22;,&#x22;label&#x22;:&#x22;Proxy Rotation&#x22;,&#x22;group&#x22;:&#x22;concepts&#x22;,&#x22;val&#x22;:2.732050807568877},{&#x22;id&#x22;:&#x22;concepts/webrtc-ip-leak&#x22;,&#x22;label&#x22;:&#x22;WebRTC IP Leak&#x22;,&#x22;group&#x22;:&#x22;concepts&#x22;,&#x22;val&#x22;:2.732050807568877},{&#x22;id&#x22;:&#x22;entities/pydoll&#x22;,&#x22;label&#x22;:&#x22;Pydoll&#x22;,&#x22;group&#x22;:&#x22;entities&#x22;,&#x22;val&#x22;:3},{&#x22;id&#x22;:&#x22;entities/firecrawl&#x22;,&#x22;label&#x22;:&#x22;Firecrawl&#x22;,&#x22;group&#x22;:&#x22;entities&#x22;,&#x22;val&#x22;:2.732050807568877},{&#x22;id&#x22;:&#x22;entities/docling&#x22;,&#x22;label&#x22;:&#x22;Docling&#x22;,&#x22;group&#x22;:&#x22;entities&#x22;,&#x22;val&#x22;:2.414213562373095},{&#x22;id&#x22;:&#x22;entities/ketch&#x22;,&#x22;label&#x22;:&#x22;ketch&#x22;,&#x22;group&#x22;:&#x22;entities&#x22;,&#x22;val&#x22;:3}],LINKS=[{&#x22;source&#x22;:&#x22;concepts/proxy-rotation&#x22;,&#x22;target&#x22;:&#x22;entities/pydoll&#x22;},{&#x22;source&#x22;:&#x22;concepts/proxy-rotation&#x22;,&#x22;target&#x22;:&#x22;concepts/webrtc-ip-leak&#x22;},{&#x22;source&#x22;:&#x22;concepts/proxy-rotation&#x22;,&#x22;target&#x22;:&#x22;concepts/web-fingerprinting&#x22;},{&#x22;source&#x22;:&#x22;concepts/web-fingerprinting&#x22;,&#x22;target&#x22;:&#x22;concepts/proxy-rotation&#x22;},{&#x22;source&#x22;:&#x22;concepts/web-fingerprinting&#x22;,&#x22;target&#x22;:&#x22;concepts/webrtc-ip-leak&#x22;},{&#x22;source&#x22;:&#x22;concepts/web-fingerprinting&#x22;,&#x22;target&#x22;:&#x22;entities/pydoll&#x22;},{&#x22;source&#x22;:&#x22;concepts/webrtc-ip-leak&#x22;,&#x22;target&#x22;:&#x22;concepts/proxy-rotation&#x22;},{&#x22;source&#x22;:&#x22;concepts/webrtc-ip-leak&#x22;,&#x22;target&#x22;:&#x22;concepts/web-fingerprinting&#x22;},{&#x22;source&#x22;:&#x22;concepts/webrtc-ip-leak&#x22;,&#x22;target&#x22;:&#x22;entities/pydoll&#x22;},{&#x22;source&#x22;:&#x22;entities/docling&#x22;,&#x22;target&#x22;:&#x22;entities/firecrawl&#x22;},{&#x22;source&#x22;:&#x22;entities/firecrawl&#x22;,&#x22;target&#x22;:&#x22;entities/pydoll&#x22;},{&#x22;source&#x22;:&#x22;entities/firecrawl&#x22;,&#x22;target&#x22;:&#x22;entities/ketch&#x22;},{&#x22;source&#x22;:&#x22;entities/ketch&#x22;,&#x22;target&#x22;:&#x22;entities/firecrawl&#x22;},{&#x22;source&#x22;:&#x22;entities/pydoll&#x22;,&#x22;target&#x22;:&#x22;concepts/web-fingerprinting&#x22;},{&#x22;source&#x22;:&#x22;entities/pydoll&#x22;,&#x22;target&#x22;:&#x22;concepts/webrtc-ip-leak&#x22;},{&#x22;source&#x22;:&#x22;entities/pydoll&#x22;,&#x22;target&#x22;:&#x22;concepts/proxy-rotation&#x22;}],CUR=&#x22;concepts/web-fingerprinting&#x22;,MAXD=3;
const C={concepts:'#8B7CF6',patterns:'#0D9373',systems:'#E0567C',syntheses:'#E2A03F',comparisons:'#3B82F6',entities:'#14B8A6',guides:'#9CA3AF'};
function lid(x){return (x&&x.id!==undefined)?x.id:x;}
const ADJ=new Map(NODES.map(function(n){return [n.id,new Set()];}));
LINKS.forEach(function(l){var s=lid(l.source),t=lid(l.target);if(ADJ.has(s)&&ADJ.has(t)){ADJ.get(s).add(t);ADJ.get(t).add(s);}});
var opt={ns:1.8,lw:0.6,ts:3.5,to:0.75,dp:2,ar:false};
function visible(){
if(!CUR)return {nodes:NODES,links:LINKS};
var dist=new Map([[CUR,0]]),fr=[CUR];
for(var d=1;d<=opt.dp;d++){var nx=[];fr.forEach(function(u){(ADJ.get(u)||[]).forEach(function(v){if(!dist.has(v)){dist.set(v,d);nx.push(v);}});});fr=nx;}
var keep=new Set(dist.keys());
return {nodes:NODES.filter(function(n){return keep.has(n.id);}),links:LINKS.filter(function(l){return keep.has(lid(l.source))&&keep.has(lid(l.target));})};
}
var el=document.getElementById('g');
var G=ForceGraph()(el).backgroundColor('#0f1117').nodeId('id')
.warmupTicks(24).cooldownTicks(70).autoPauseRedraw(true)
.nodeColor(function(n){return C[n.group]||'#9CA3AF';}).nodeLabel('label').nodeVal(function(n){return n.val;})
.linkColor(function(){return 'rgba(255,255,255,0.12)';})
.nodeRelSize(opt.ns).linkWidth(opt.lw)
.linkDirectionalArrowLength(0).linkDirectionalArrowRelPos(1).linkDirectionalArrowColor(function(){return 'rgba(255,255,255,0.4)';})
.nodeCanvasObjectMode(function(){return 'after';})
.nodeCanvasObject(function(n,ctx,scale){var r=opt.ns*Math.sqrt(n.val||1);
if(n.id===CUR){ctx.beginPath();ctx.arc(n.x,n.y,r+1.6,0,6.283);ctx.strokeStyle='#fff';ctx.lineWidth=1.2/scale;ctx.stroke();}
if(opt.to>0&&opt.ts>0){var t=n.label.length>28?n.label.slice(0,26)+'…':n.label;ctx.globalAlpha=opt.to;ctx.font=((n.id===CUR?opt.ts+1:opt.ts))+'px ui-sans-serif,sans-serif';ctx.fillStyle=(n.id===CUR)?'#ffffff':'#aab0c0';ctx.textAlign='center';ctx.textBaseline='top';ctx.fillText(t,n.x,n.y+r+1.5);ctx.globalAlpha=1;}})
.onNodeClick(function(n){if(window.top){window.top.location.href='/'+n.id;}});
G.graphData(visible());G.d3VelocityDecay(0.4);
function fit(){G.zoomToFit(400,20);}
setTimeout(fit,350);setTimeout(fit,1100);
// Stop the render/sim loop while idle so the fixed widget never repaints during
// parent-page scroll; resume only while the pointer is over the widget.
var pt;function pause(){G.pauseAnimation();}function resume(){G.resumeAnimation();}
function idle(ms){clearTimeout(pt);pt=setTimeout(pause,ms);}
document.body.addEventListener('pointerenter',function(){clearTimeout(pt);resume();});
document.body.addEventListener('pointerleave',function(){idle(250);});
addEventListener('resize',function(){resume();G.zoomToFit(0,20);idle(700);});
idle(2000);
function apply(re){resume();G.nodeRelSize(opt.ns).linkWidth(opt.lw).linkDirectionalArrowLength(opt.ar?2.6:0);if(re){G.graphData(visible());setTimeout(fit,450);}idle(re?2200:1400);}
function bind(id,key,fmt,re){var e=document.getElementById(id),o=document.getElementById('v'+id);e.value=opt[key];if(o)o.textContent=fmt(opt[key]);e.addEventListener('input',function(){opt[key]=parseFloat(e.value);if(o)o.textContent=fmt(opt[key]);apply(re);});}
bind('ns','ns',function(v){return v.toFixed(1);},false);
bind('lw','lw',function(v){return v.toFixed(1);},false);
bind('ts','ts',function(v){return v.toFixed(1);},false);
bind('to','to',function(v){return v.toFixed(2);},false);
var dE=document.getElementById('dp'),dO=document.getElementById('vd');dE.max=MAXD;dE.value=opt.dp;dO.textContent=opt.dp;dE.addEventListener('input',function(){opt.dp=parseInt(dE.value,10);dO.textContent=opt.dp;apply(true);});
if(!CUR)document.getElementById('depthRow').style.display='none';
var aE=document.getElementById('ar');aE.checked=opt.ar;aE.addEventListener('change',function(){opt.ar=aE.checked;apply(false);});
document.getElementById('gear').addEventListener('click',function(){document.getElementById('panel').classList.toggle('open');});
var hd=document.getElementById('hd');hd.textContent='⠿  '+(CUR?'Local graph':'Knowledge graph');
// free-form placement: drag by the header. Default is bottom-right (inline style);
// a moved position is saved per parent-origin and restored on every page.
function clampPos(fe,l,t){var TW=(window.top||window),r=fe.getBoundingClientRect();return [Math.min(Math.max(0,l),Math.max(0,TW.innerWidth-r.width)),Math.min(Math.max(0,t),Math.max(0,TW.innerHeight-r.height))];}
function place(fe,l,t){var p=clampPos(fe,l,t);fe.style.left=p[0]+'px';fe.style.top=p[1]+'px';fe.style.right='auto';fe.style.bottom='auto';}
try{var sp=JSON.parse(localStorage.getItem('llmwiki_graph_pos'));if(sp&&window.frameElement)place(window.frameElement,sp.l,sp.t);}catch(e){if(window.console)console.debug('graph: saved position unavailable',e);}
hd.addEventListener('pointerdown',function(e){var fe=window.frameElement;if(!fe)return;var rect=fe.getBoundingClientRect();var sx=e.screenX,sy=e.screenY,L=rect.left,T=rect.top;place(fe,L,T);hd.setPointerCapture(e.pointerId);
function mv(ev){place(fe,L+ev.screenX-sx,T+ev.screenY-sy);}
function up(){if(hd.hasPointerCapture(e.pointerId))hd.releasePointerCapture(e.pointerId);hd.removeEventListener('pointermove',mv);hd.removeEventListener('pointerup',up);try{localStorage.setItem('llmwiki_graph_pos',JSON.stringify({l:parseFloat(fe.style.left),t:parseFloat(fe.style.top)}));}catch(e2){if(window.console)console.debug('graph: could not persist position',e2);}}
hd.addEventListener('pointermove',mv);hd.addEventListener('pointerup',up);e.preventDefault();});
</script></body></html>"
  title="Knowledge graph"
  loading="lazy"
  style={{position:"fixed",right:"18px",bottom:"18px",width:"320px",height:"340px",border:0,borderRadius:"14px",boxShadow:"0 6px 28px rgba(0,0,0,0.38)",zIndex:50,background:"#0f1117"}}
/>
