surf
- title
- surf
- type
- toolbox
- summary
- Go HTTP client impersonating Chrome and Firefox down to the TLS handshake and QUIC packets
- tags
- go, http-client, tls-fingerprinting, browser-impersonation, scraping, quic
- language
- Go
- license
- MIT
- created
- 2026-05-06
- updated
- 2026-05-06
Go HTTP client library that impersonates real browsers down to the TLS handshake and the QUIC initial packet. Targets the same niche as curl-impersonate and tls-client: web automation and scraping against sites that fingerprint clients (Cloudflare, Akamai, PerimeterX, DataDome). Mimics Chrome v145 and Firefox v148 across Windows, macOS, Linux, Android, and iOS, with HTTP/2 SETTINGS frames, header ordering, and full HTTP/3 over QUIC.
What it does
Standard net/http.Client is fingerprintable: its TLS ClientHello, HTTP/2 SETTINGS, and header order are constant and don't match any real browser. Anti-bot vendors compute JA3/JA4 hashes from the TLS handshake and HTTP/2 fingerprints from frame contents, and Go traffic stands out. SURF replaces the transport with one that produces byte-equivalent handshakes to a chosen browser version + OS combo, including the QUIC variant for HTTP/3-only sites.
The library also wraps the request lifecycle in a fluent builder API and a Result-type-based error model (g.Result[T]) borrowed from enetx/g, so it doesn't feel like stdlib net/http.
How it works
TLS fingerprinting rides on uTLS (refraction-networking/utls), the standard Go fork that exposes ClientHello specs. SURF picks HelloID or full HelloSpec per browser/OS combo. JA3/JA4 fingerprints can also be set explicitly or randomized.
QUIC/HTTP/3 uses quic-go with browser-matched parameters: header ordering, frame ordering, SETTINGS values, and a JA4QUIC fingerprint built from the Initial Packet plus TLS ClientHello. SOCKS5 UDP proxies are supported so HTTP/3 stays available behind a proxy; HTTP proxies trigger automatic fallback to HTTP/2.
HTTP/2 is configurable at the SETTINGS-frame level: header table size, push enable, initial window size, max concurrent streams, etc. โ set per browser preset to match the real values Chrome and Firefox send.
Header ordering is preserved end-to-end. Most Go HTTP libraries normalize header order via the standard library's http.Header map; SURF uses the enetx/http fork that keeps order, because anti-bot systems treat header sequence as a fingerprint.
Builder API chains configuration: surf.NewClient().Builder().Impersonate().Chrome().JA().Randomized().HTTP2Settings()...Build(). Middleware plugs in via .With(fn, priority...) for request, response, or client-scope hooks.
Std() escape hatch converts a configured SURF client back into *net/http.Client for use with libraries that want stdlib types. Most fingerprinting features survive the conversion; retry logic, response caching, remote-address tracking, and timing don't.
Foundation stack: enetx/http (forked stdlib HTTP), quic-go, uTLS, enetx/g (Result/Option types and string utilities).
Usage
Basic Chrome impersonation:
client := surf.NewClient().
Builder().
Impersonate().Chrome().
Build().Unwrap()
resp := client.Get("https://example.com").Do()
if resp.IsOk() {
fmt.Println(resp.Ok().Body.String().Unwrap())
}
iOS Chrome with HTTP/3 forced and a SOCKS5 UDP proxy:
client := surf.NewClient().
Builder().
Impersonate().IOS().Chrome().
Proxy("socks5://127.0.0.1:1080").
ForceHTTP3().
Build().Unwrap()
Session with cookie jar, retry, and body cache:
client := surf.NewClient().
Builder().
Impersonate().Firefox().
Session().
Retry(3, 2*time.Second).
CacheBody().
Build().Unwrap()
Multipart upload with mixed sources:
mp := surf.NewMultipart().
Field("description", "Files").
File("document", g.NewFile("/path/doc.pdf")).
FileBytes("data", "data.json", g.Bytes(`{"k":"v"}`))
resp := client.Post("https://api.example.com/upload").Multipart(mp).Do()
Limitations
- Requires Go 1.25+.
- Chrome v145 and Firefox v148 are the impersonation targets; staying current with new browser releases is on the maintainer.
- Result-type API (
g.Result[T]from enetx/g) is not idiomatic Go and pulls a non-trivial generic-utilities dependency. Existing codebases need adapter shims. - Single-author project (Enetx). Active but bus-factor-1.
- The Std() escape hatch loses retry, body caching, remote address, and timing info.
- TLS fingerprinting via uTLS is a moving target โ anti-bot vendors update detection regularly, and a working impersonation today doesn't guarantee one next quarter.
Cross-references
Lives in the same anti-fingerprinting space as the proxy core in sing-box, the VLESS-cluster tutorials in xray-tutorial, and the broader russia-vpn-bypass-state-2026-04 toolkit โ though those operate at the transport-protocol layer while SURF operates at the application HTTP layer. Useful for agent harnesses building scrapers; see sandboxing-ai-agents for the orchestration side.
Repo
github.com/enetx/surf โ Go, MIT, 1.7k stars (88 forks). Single-author, actively maintained.