# surf

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:

```go
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:

```go
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:

```go
client := surf.NewClient().
    Builder().
    Impersonate().Firefox().
    Session().
    Retry(3, 2*time.Second).
    CacheBody().
    Build().Unwrap()
```

Multipart upload with mixed sources:

```go
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](https://github.com/enetx/surf) — Go, MIT, 1.7k stars (88 forks). Single-author, actively maintained.
