Every Byte of your Request Indistinguishable from Chrome.
Bot detection doesn't just check your User-Agent anymore.
It fingerprints your TLS handshake. Your HTTP/2 frames. Your QUIC parameters. The order of your headers. Whether your SNI is encrypted.
One mismatch = blocked.
import httpcloak
r = httpcloak.get("https://target.com", preset="chrome-143")That's it. Full browser transport layer fingerprint.
|
|
|
βββββββββββββββββββββββββββββββββββ
β ECH (Encrypted Client Hello) β
βββββββββββββββββββββββββββββββββββ€
β WITHOUT: sni=plaintext β
β WITH: sni=encrypted + β
βββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββ
β HTTP/3 Fingerprint Match β
βββββββββββββββββββββββββββββββββββ€
β Protocol: h3 + β
β QUIC Version: 1 + β
β Transport Params: + β
β GREASE Frames: + β
βββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ
β BOTH LIBRARIES β HTTPCLOAK ONLY β
ββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββ€
β β β
β + TLS fingerprint (JA3/JA4) β + HTTP/3 fingerprinting β
β + HTTP/2 fingerprint β + ECH (encrypted SNI) β
β + Post-quantum TLS β + MASQUE proxy β
β + Bot score: 99 β + Domain fronting β
β β + Certificate pinning β
β β + Go, Python, Node.js, C# β
β β β
ββββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββ
pip install httpcloak # Python
npm install httpcloak # Node.js
go get github.com/sardanioss/httpcloak # Go
dotnet add package HttpCloak # C#import httpcloak
# Simple request
r = httpcloak.get("https://example.com", preset="chrome-143")
print(r.status_code, r.protocol)
# POST with JSON
r = httpcloak.post("https://httpbin.org/post",
json={"key": "value"},
preset="chrome-143"
)
# Custom headers
r = httpcloak.get("https://httpbin.org/headers",
headers={"X-Custom": "value"},
preset="chrome-143"
)import (
"context"
"github.com/sardanioss/httpcloak/client"
)
// Simple request
c := client.NewClient("chrome-143")
defer c.Close()
resp, _ := c.Get(ctx, "https://example.com", nil)
body, _ := resp.Text()
fmt.Println(resp.StatusCode, resp.Protocol)
// POST with JSON
jsonBody := []byte(`{"key": "value"}`)
resp, _ = c.Post(ctx, "https://httpbin.org/post",
bytes.NewReader(jsonBody),
map[string][]string{"Content-Type": {"application/json"}},
)
// Custom headers
resp, _ = c.Get(ctx, "https://httpbin.org/headers", map[string][]string{
"X-Custom": {"value"},
})import httpcloak from "httpcloak";
// Simple request
const session = new httpcloak.Session({ preset: "chrome-143" });
const r = await session.get("https://example.com");
console.log(r.statusCode, r.protocol);
// POST with JSON
const r = await session.post("https://httpbin.org/post", {
json: { key: "value" }
});
// Custom headers
const r = await session.get("https://httpbin.org/headers", {
headers: { "X-Custom": "value" }
});
session.close();using HttpCloak;
// Simple request
using var session = new Session(preset: Presets.Chrome143);
var r = session.Get("https://example.com");
Console.WriteLine($"{r.StatusCode} {r.Protocol}");
// POST with JSON
var r = session.Post("https://httpbin.org/post",
json: new { key = "value" }
);
// Custom headers
var r = session.Get("https://httpbin.org/headers",
headers: new Dictionary<string, string> { ["X-Custom"] = "value" }
);Hides which domain you're connecting to from network observers.
session = httpcloak.Session(
preset="chrome-143",
ech_from="cloudflare.com" # Fetches ECH config from DNS
)Cloudflare trace shows sni=encrypted instead of sni=plaintext.
Two methods for QUIC through proxies:
| Method | How it works |
|---|---|
| SOCKS5 UDP ASSOCIATE | Proxy relays UDP packets. Most residential proxies support this. |
| MASQUE (CONNECT-UDP) | RFC 9298. Tunnels UDP over HTTP/3. Premium providers only. |
# SOCKS5 with UDP
session = httpcloak.Session(proxy="socks5://user:pass@proxy:1080")
# MASQUE
session = httpcloak.Session(proxy="masque://proxy:443")Known MASQUE providers (auto-detected): Bright Data, Oxylabs, Smartproxy, SOAX.
Connect to a different host than what appears in TLS SNI.
client := httpcloak.NewClient("chrome-143",
httpcloak.WithConnectTo("public-cdn.com", "actual-backend.internal"),
)client.PinCertificate("sha256/AAAA...",
httpcloak.PinOptions{IncludeSubdomains: true})client.OnPreRequest(func(req *http.Request) error {
req.Header.Set("X-Custom", "value")
return nil
})
client.OnPostResponse(func(resp *httpcloak.Response) {
log.Printf("Got %d from %s", resp.StatusCode, resp.FinalURL)
})fmt.Printf("DNS: %dms, TCP: %dms, TLS: %dms, Total: %dms\n",
resp.Timing.DNSLookup,
resp.Timing.TCPConnect,
resp.Timing.TLSHandshake,
resp.Timing.Total)session = httpcloak.Session(preset="chrome-143", http_version="h3") # Force HTTP/3
session = httpcloak.Session(preset="chrome-143", http_version="h2") # Force HTTP/2
session = httpcloak.Session(preset="chrome-143", http_version="h1") # Force HTTP/1.1Auto mode tries HTTP/3 first, falls back gracefully.
# Stream large downloads
stream = session.get_stream("https://example.com/large-file.zip")
print(f"Size: {stream.content_length} bytes")
with open("file.zip", "wb") as f:
while True:
chunk = stream.read(8192)
if not chunk:
break
f.write(chunk)
stream.close()
# Iterator pattern
for chunk in session.get_stream(url).iter_content(chunk_size=8192):
process(chunk)
# Multipart upload
r = session.post(url, files={
"file": ("filename.jpg", file_bytes, "image/jpeg")
})# Basic auth
r = httpcloak.get("https://api.example.com/data",
auth=("username", "password"),
preset="chrome-143"
)
# Session-level auth
session = httpcloak.Session(
preset="chrome-143",
auth=("username", "password")
)# Timeout
session = httpcloak.Session(preset="chrome-143", timeout=30)
# Per-request timeout
r = session.get("https://slow-api.com/data", timeout=60)// Go: Timeout and retry configuration
client := client.NewClient("chrome-143",
client.WithTimeout(30 * time.Second),
client.WithRetry(3), // Retry 3 times on 429, 500, 502, 503, 504
client.WithRetryConfig(
5, // Max retries
500 * time.Millisecond, // Min backoff
10 * time.Second, // Max backoff
[]int{429, 503}, // Status codes to retry
),
)// Disable automatic redirects
client := client.NewClient("chrome-143",
client.WithoutRedirects(),
)
resp, _ := client.Get(ctx, "https://example.com/redirect", nil)
fmt.Println(resp.StatusCode) // 302
fmt.Println(resp.GetHeader("location")) // Redirect URLimport httpcloak
# Module-level functions
httpcloak.get(url, **kwargs)
httpcloak.post(url, **kwargs)
httpcloak.put(url, **kwargs)
httpcloak.patch(url, **kwargs)
httpcloak.delete(url, **kwargs)
httpcloak.head(url, **kwargs)
httpcloak.options(url, **kwargs)
# Session class
session = httpcloak.Session(
preset="chrome-143", # Browser preset
proxy="socks5://...", # Proxy URL
timeout=30, # Timeout in seconds
http_version="h3", # Force protocol: h1, h2, h3, auto
ech_from="cloudflare.com", # ECH config source
auth=("user", "pass"), # Basic auth
)
# Session methods
session.get(url, **kwargs)
session.post(url, data=None, json=None, **kwargs)
session.get_stream(url) # Streaming download
session.close()
# Response object
response.status_code # HTTP status
response.ok # True if status < 400
response.text # Body as string
response.content # Body as bytes
response.json() # Parse JSON
response.headers # Response headers
response.protocol # h1, h2, or h3
response.url # Final URL
response.raise_for_status() # Raise on 4xx/5xximport "github.com/sardanioss/httpcloak/client"
// Client creation
c := client.NewClient("chrome-143",
client.WithTimeout(30 * time.Second),
client.WithProxy("socks5://..."),
client.WithRetry(3),
client.WithoutRedirects(),
client.WithInsecureSkipVerify(),
)
defer c.Close()
// Request methods
resp, err := c.Get(ctx, url, headers)
resp, err := c.Post(ctx, url, body, headers)
resp, err := c.Put(ctx, url, body, headers)
resp, err := c.Delete(ctx, url, headers)
// Advanced request
resp, err := c.Do(ctx, &client.Request{
Method: "GET",
URL: url,
Headers: map[string][]string{},
Body: io.Reader,
Params: map[string]string{},
ForceProtocol: client.ProtocolHTTP3,
FetchMode: client.FetchModeCORS,
Referer: "https://example.com",
})
// Response object
resp.StatusCode
resp.Protocol
resp.Headers
resp.Body // io.ReadCloser
resp.Text() // (string, error)
resp.Bytes() // ([]byte, error)
resp.JSON(&v) // error
resp.GetHeader(key) // string
resp.IsSuccess() // bool
resp.IsRedirect() // boolimport httpcloak from "httpcloak";
// Session creation
const session = new httpcloak.Session({
preset: "chrome-143",
proxy: "socks5://...",
timeout: 30000,
httpVersion: "h3",
});
// Async methods
await session.get(url, options)
await session.post(url, { json, data, headers })
await session.put(url, options)
await session.delete(url, options)
// Sync methods
session.getSync(url, options)
session.postSync(url, options)
session.close()
// Response object
response.statusCode
response.ok
response.text
response.json()
response.headers
response.protocolusing HttpCloak;
// Session creation
var session = new Session(
preset: Presets.Chrome143,
proxy: "socks5://...",
timeout: 30
);
// Request methods
session.Get(url, headers)
session.Post(url, json: obj, data: dict, headers: dict)
session.Put(url, ...)
session.Delete(url)
session.Dispose()
// Response object
response.StatusCode
response.Ok
response.Text
response.Json<T>()
response.Headers
response.Protocol| Preset | Platform | PQ | H3 |
|---|---|---|---|
chrome-143 |
Auto | β | β |
chrome-143-windows |
Windows | β | β |
chrome-143-macos |
macOS | β | β |
chrome-143-linux |
Linux | β | β |
firefox-133 |
Auto | β | β |
chrome-mobile-android |
Android | β | β |
chrome-mobile-ios |
iOS | β | β |
PQ = Post-Quantum (X25519MLKEM768) Β· H3 = HTTP/3
| Tool | Tests |
|---|---|
| tls.peet.ws | JA3, JA4, HTTP/2 Akamai |
| quic.browserleaks.com | HTTP/3 QUIC fingerprint |
| cf.erisa.uk | Cloudflare bot score |
| cloudflare.com/cdn-cgi/trace | ECH status, TLS version |
Custom forks for browser-accurate fingerprinting:
- sardanioss/utls β TLS fingerprinting
- sardanioss/quic-go β HTTP/3 fingerprinting
- sardanioss/net β HTTP/2 frame fingerprinting
- Discord: sardanioss
- Email: [email protected]
MIT License
