Documentation
¶
Index ¶
- Variables
- func ChromeFetchHeaderOrder(custom []string) []string
- func ChromeHeaders() map[string]string
- func ChromeHeadersFor(p BrowserProfile) map[string]string
- func ClientHintsHeaders(ua string) map[string]string
- func DefaultHeaderOrderFor(browser string) []string
- func ExtractChromeVersion(ua string) string
- func IsRetryable(err error) bool
- func IsRetryableStatus(code int) bool
- func MaskProxy(proxyURL string) string
- func ParseRetryAfter(resp *http.Response) time.Duration
- func RandomUserAgent() string
- func RetryDo[T any](ctx context.Context, rc RetryConfig, fn func() (T, error)) (T, error)
- func RetryDoWithReset[T any](ctx context.Context, rc RetryConfig, resetFn func(), fn func() (T, error)) (T, error)
- func RetryHTTP(ctx context.Context, rc RetryConfig, fn func() (*http.Response, error)) (*http.Response, error)
- func RetryHTTPWithReset(ctx context.Context, rc RetryConfig, resetFn func(), ...) (*http.Response, error)
- func UserAgentForProfile(p TLSProfile) string
- func ValidateProxy(client *BrowserClient, testURL string) error
- func WithRetryHook(ctx context.Context, fn RetryHookFunc) context.Context
- type AnalyzeResponse
- type AnalyzeTech
- type BackendConfig
- type BackendFactory
- type BackoffConfig
- type BrowserClient
- func (bc *BrowserClient) ChromeHeaders() map[string]string
- func (bc *BrowserClient) Close() error
- func (bc *BrowserClient) CloseIdleConnections()
- func (bc *BrowserClient) Do(method, urlStr string, headers map[string]string, body io.Reader) ([]byte, map[string]string, int, error)
- func (bc *BrowserClient) DoCtx(ctx context.Context, method, urlStr string, headers map[string]string, ...) ([]byte, map[string]string, int, error)
- func (bc *BrowserClient) DoWithHeaderOrder(method, urlStr string, headers map[string]string, body io.Reader, ...) ([]byte, map[string]string, int, error)
- func (bc *BrowserClient) DoWithHeaderOrderCtx(ctx context.Context, method, urlStr string, headers map[string]string, ...) ([]byte, map[string]string, int, error)
- func (bc *BrowserClient) GetCookieValue(rawURL, name string) string
- func (bc *BrowserClient) Identity() BrowserIdentity
- func (bc *BrowserClient) RoundTrip(req *http.Request) (*http.Response, error)
- func (bc *BrowserClient) SetProxy(proxyURL string) error
- func (bc *BrowserClient) StdClient() *http.Client
- func (bc *BrowserClient) Use(mw ...Middleware)
- type BrowserIdentity
- type BrowserProfile
- type ByparrConfig
- type ByparrSolver
- type ChallengeType
- type ClientOption
- func WithBackend(factory BackendFactory) ClientOption
- func WithCookieJar(jar http.CookieJar) ClientOption
- func WithCookieSolver(provider CookieProvider) ClientOption
- func WithDebug() ClientOption
- func WithDialControl(fn func(network, address string) error) ClientOption
- func WithFetchMode() ClientOption
- func WithFollowRedirects() ClientOption
- func WithHTTP3() ClientOption
- func WithHeaderOrder(order []string) ClientOption
- func WithIdentity(id BrowserIdentity) ClientOption
- func WithInsecureSkipVerify() ClientOption
- func WithOxBrowser(url string) ClientOption
- func WithProfile(p TLSProfile) ClientOption
- func WithProxy(url string) ClientOption
- func WithProxyPool(pool ProxyPoolProvider) ClientOption
- func WithRedirectGuard(fn func(req *http.Request, via []*http.Request) error) ClientOption
- func WithRequestURLGuard(fn func(ctx context.Context, u *url.URL) error) ClientOption
- func WithRetryOnBlock(n int) ClientOption
- func WithStdHTTP() ClientOption
- func WithTimeout(seconds int) ClientOption
- func WithWebshareCountry(apiKey string, countries ...string) ClientOption
- func WithWebshareRotating(username, password string, countries ...string) ClientOption
- func WithoutSSRFGuard() ClientOption
- type CloudflareError
- type CookieProvider
- type FetchSmartResponse
- type HTTPDoer
- type Handler
- type HttpStatusError
- type Jitter
- type Middleware
- func Chain(middlewares ...Middleware) Middleware
- func CloudflareCookieMiddleware(provider CookieProvider) Middleware
- func RateLimitMiddleware(limiter *ratelimit.DomainLimiter) Middleware
- func RateLimitMiddlewareWithContext(ctx context.Context, limiter *ratelimit.DomainLimiter) Middleware
- func RetryMiddleware(cfg RetryConfig) Middleware
- func RetryMiddlewareWithContext(ctx context.Context, cfg RetryConfig) Middleware
- func SmartFetchMiddleware(oxClient *OxBrowserClient) Middleware
- type OxBrowserClient
- type OxBrowserSolver
- type OxBrowserSolverConfig
- type ProfileOption
- type ProxyPoolProvider
- type Request
- type Response
- type RetryConfig
- type RetryHookFunc
- type RetryTracker
- type SiteInfo
- type SiteIntel
- type SolveResponse
- type TLSProfile
Constants ¶
This section is empty.
Variables ¶
var BuiltinProfiles = []BrowserProfile{ { UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36", TLSProfile: ProfileChrome156, Browser: "chrome", OS: "windows", }, { UserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36", TLSProfile: ProfileChrome156, Browser: "chrome", OS: "macos", }, { UserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36", TLSProfile: ProfileChrome156, Browser: "chrome", OS: "linux", }, { UserAgent: "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Mobile Safari/537.36", TLSProfile: ProfileChrome156, Browser: "chrome", OS: "android", Mobile: true, }, { UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36", TLSProfile: ProfileChrome152, Browser: "chrome", OS: "windows", }, { UserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36", TLSProfile: ProfileChrome152, Browser: "chrome", OS: "macos", }, { UserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36", TLSProfile: ProfileChrome152, Browser: "chrome", OS: "linux", }, { UserAgent: "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Mobile Safari/537.36", TLSProfile: ProfileChrome152, Browser: "chrome", OS: "android", Mobile: true, }, { UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36", TLSProfile: ProfileChrome150, Browser: "chrome", OS: "windows", }, { UserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36", TLSProfile: ProfileChrome150, Browser: "chrome", OS: "macos", }, { UserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36", TLSProfile: ProfileChrome150, Browser: "chrome", OS: "linux", }, { UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36", TLSProfile: ProfileChrome146, Browser: "chrome", OS: "windows", }, { UserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36", TLSProfile: ProfileChrome146, Browser: "chrome", OS: "macos", }, { UserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36", TLSProfile: ProfileChrome146, Browser: "chrome", OS: "linux", }, { UserAgent: "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Mobile Safari/537.36", TLSProfile: ProfileChrome146, Browser: "chrome", OS: "android", Mobile: true, }, { UserAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1", TLSProfile: ProfileSafariIOS26, Browser: "safari", OS: "ios", Mobile: true, }, { UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:148.0) Gecko/20100101 Firefox/148.0", TLSProfile: ProfileFirefox148, Browser: "firefox", OS: "windows", }, { UserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:148.0) Gecko/20100101 Firefox/148.0", TLSProfile: ProfileFirefox148, Browser: "firefox", OS: "macos", }, { UserAgent: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", TLSProfile: ProfileFirefox148, Browser: "firefox", OS: "linux", }, { UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36", TLSProfile: ProfileBrave146, Browser: "brave", OS: "windows", }, { UserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36", TLSProfile: ProfileBrave146, Browser: "brave", OS: "macos", }, { UserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36", TLSProfile: ProfileBrave146, Browser: "brave", OS: "linux", }, { UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36 Edg/152.0.0.0", TLSProfile: ProfileChrome152, Browser: "edge", OS: "windows", }, { UserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36 Edg/152.0.0.0", TLSProfile: ProfileChrome152, Browser: "edge", OS: "macos", }, }
BuiltinProfiles is the set of current browser request profiles, with per-OS variants. Every entry describes ONE browser: the TLS profile major, the User-Agent major and the Client Hints derived from it agree (pinned by TestBuiltinProfilesCoherent). RandomProfile and PlatformMatchedProfile draw only from this set; the legacy set below stays reachable through UserAgentForProfile / Identity() for callers that pin an older profile.
The newest Chrome here is bounded by what has been measured, not by the current Chrome release: a UA newer than its TLS profile would describe two different browsers. chrome_156 reuses tls-client's Chrome_152 because that profile's fingerprint was measured identical to Chrome 156's.
"sec-ch-ua", "sec-ch-ua-mobile", "sec-ch-ua-platform",
"upgrade-insecure-requests", "user-agent", "accept",
"sec-fetch-site", "sec-fetch-mode", "sec-fetch-user", "sec-fetch-dest",
"referer", "accept-encoding", "accept-language", "cookie", "priority",
}
ChromeNavigationHeaderOrder is the request-header order of a top-level navigation in stable Google Chrome 156, taken from a capture of Google Chrome 156.0.8078.80 (testdata/wire/google_chrome_156_nav.json). It is the order the default client (chrome_156) uses.
It differs from DefaultHeaderOrder in one place: accept-language comes after accept-encoding. DefaultHeaderOrder was captured from Chrome for Testing, which puts accept-language right after sec-ch-ua-platform; branded Google Chrome does not. The difference follows the build, not the version (Chrome for Testing 131, 152 and 156 all have the early placement), so every modern Chrome profile whose TLS extension order is shuffled (chrome_146, 150, 152, 156) uses this order, and chrome_131, 133 and 144 keep DefaultHeaderOrder. Branded 146 to 152 were not captured themselves; the placement is carried over from the branded 156 capture on the build argument.
referer and cookie are absent from a first request, so the capture does not place them; they take the positions they have in the fetch captures (referer before accept-encoding, cookie between accept-language and priority), which share the same network stack.
var DefaultBackoff = BackoffConfig{ InitialWait: 500 * time.Millisecond, MaxWait: 10 * time.Second, Multiplier: 2.0, JitterPct: 0.3, }
DefaultBackoff is the standard backoff for API retry loops.
var DefaultHeaderOrder = []string{
"sec-ch-ua",
"sec-ch-ua-mobile",
"sec-ch-ua-platform",
"accept-language",
"upgrade-insecure-requests",
"user-agent",
"accept",
"sec-fetch-site",
"sec-fetch-mode",
"sec-fetch-user",
"sec-fetch-dest",
"accept-encoding",
"priority",
"referer",
"cookie",
}
DefaultHeaderOrder is the Chrome request-header order, taken from the real-Chrome references in internal/fingerprint/testdata/reference_chrome_*.json (header_order field). Real Chrome sends these 13 headers in this exact order on a top-level GET navigation. referer and cookie are not in the reference (the capture was a clean first request) but are appended so they stay ordered when a caller supplies them; their position is approximate — the references do not record it.
var DefaultJitter = pacing.DefaultJitter
DefaultJitter is the canonical anti-fingerprint jitter (500ms–2.5s), backed by pacing.DefaultJitter. Callers that need a different range should construct their own pacing.Jitter.
var DefaultRetryConfig = RetryConfig{ MaxRetries: 3, InitialWait: 500 * time.Millisecond, MaxWait: 10 * time.Second, Multiplier: 2.0, }
DefaultRetryConfig is suitable for most HTTP calls.
var ErrFetchModeUnsupported = errors.New("stealth: fetch mode needs a Chrome-family profile")
ErrFetchModeUnsupported is returned by NewClient when WithFetchMode is used with a profile outside the Chrome family.
var ErrSSRFBlocked = errors.New("stealth: SSRF-blocked address")
ErrSSRFBlocked wraps every error the built-in default-deny guards return when a fetch target resolves to loopback, private (RFC1918 / RFC4193 ULA), link-local (including the cloud-metadata address 169.254.169.254), or the unspecified address — the address classes an SSRF payload targets to reach internal infrastructure from a caller-supplied URL (an advertiser-provided website, a redirect Location header, a scraped image src).
This is a deliberately dependency-free, stdlib-only floor: a BrowserClient is fail-closed BY CONSTRUCTION (a forgotten guard config denies internal targets), and the public option signatures stay free of any framework or bogdanfinn/fhttp type. A consumer that wants the fleet's fuller SSRF policy (CGNAT, NAT64/6to4, alt-encoded-IP rejection) wires go-kit/httputil's SSRFGuards()/CheckURL through WithDialControl / WithRedirectGuard / WithRequestURLGuard, which OVERRIDE these defaults.
A caller-supplied guard's error need NOT wrap this sentinel itself: the With* setters tag it (see tagGuardErr2), so errors.Is(err, ErrSSRFBlocked) holds for any configured guard's rejection, built-in or framework-wired. That is what makes doWithRetry treat the rejection as non-retryable — a blocked target is about the URL/address, not the proxy, so every retry re-blocks identically.
var ErrUnknownTLSProfile = errors.New("stealth: unknown TLS profile")
ErrUnknownTLSProfile is returned when a TLSProfile has no backend mapping. An unmapped profile must never quietly become a different browser, so client construction fails instead of substituting one.
var FirefoxHeaderOrder = []string{
"user-agent",
"accept",
"accept-language",
"accept-encoding",
"referer",
"cookie",
"upgrade-insecure-requests",
"sec-fetch-dest",
"sec-fetch-mode",
"sec-fetch-site",
"sec-fetch-user",
"priority",
"te",
}
FirefoxHeaderOrder is the Firefox request-header order on a top-level GET navigation over HTTP/2, as observed from a real Firefox. cookie was captured between accept-encoding and upgrade-insecure-requests; referer is placed just before it (its position was not captured).
var RandomUserAgents = []string{
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15",
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:148.0) Gecko/20100101 Firefox/148.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:148.0) Gecko/20100101 Firefox/148.0",
}
RandomUserAgents is a pool of browser User-Agents for rotation, for callers that do not care which browser they look like (connectivity probes). Its Chrome entries describe ONLY the default TLS profile (ProfileChrome156); pairing a User-Agent from here with a client built on another profile advertises a different browser than the one fingerprinted. Use (*BrowserClient).Identity() or ChromeHeadersFor to get a matching one. Firefox entries are kept in line with ProfileFirefox148. The Safari 17.2 entry is stale relative to BuiltinProfiles (17.6/18.2) but out of scope here — left as-is.
Functions ¶
func ChromeFetchHeaderOrder ¶ added in v1.27.0
ChromeFetchHeaderOrder returns the request-header order of a fetch() or XMLHttpRequest in stable Google Chrome 156, for use with WithHeaderOrder or DoWithHeaderOrder. custom lists the page's own headers (authorization, x-csrf-token, ...) in the order you send them; they are placed as one block after sec-ch-ua-platform, before the rest.
Headers missing from an order are sent after the listed ones in alphabetical order (fhttp), so a custom header you leave out ends up after priority. A client built with WithFetchMode and no WithHeaderOrder uses ChromeFetchHeaderOrder(nil).
What is established and what is not. Established, from the captures testdata/wire/google_chrome_156_fetch.json and the Chrome for Testing fetches: sec-ch-ua-platform first, user-agent after the page's headers, then accept, sec-fetch-site, sec-fetch-mode, sec-fetch-dest, referer, accept-encoding, accept-language (branded Chrome only), cookie, priority. Not established: where Chrome interleaves the page's own headers with sec-ch-ua, sec-ch-ua-mobile and user-agent. In the capture they are spread between them in a pattern that one sample cannot generalise (and sec-ch-ua-mobile and user-agent swap places when the page sets no headers); this function puts the page's headers in one block. A caller that has captured its own target's exact order should pass that order itself.
func ChromeHeaders ¶
ChromeHeaders returns Chrome navigation headers for the DEFAULT client profile: the User-Agent is a random desktop-OS variant of the browser that NewClient() presents when no WithProfile/WithIdentity is given, so its major matches the default TLS fingerprint. It does not describe a client built with another profile; for that use ChromeHeadersFor or (*BrowserClient).ChromeHeaders.
The set and the accept value match the real-Chrome references captured per major in internal/fingerprint/testdata/reference_chrome_*.json (header_order + accept fields). Real Chrome sends 13 request headers on a top-level GET navigation; the sec-ch-ua-* trio is added separately by ClientHintsHeaders (it is profile-version-dependent), so ChromeHeaders emits the remaining 10. The sec-fetch-*, upgrade-insecure-requests, and priority values are the canonical Chrome top-level-navigation values; the references record only these headers' names and order (in header_order), not their values, and the oracle compares only names+order for them.
func ChromeHeadersFor ¶ added in v1.26.1
func ChromeHeadersFor(p BrowserProfile) map[string]string
ChromeHeadersFor returns Chrome navigation headers that describe profile p: its own User-Agent plus the sec-ch-ua-* Client Hints derived from it, so the advertised browser matches the TLS fingerprint p.TLSProfile. Use it with the profile the client was built with (BuiltinProfiles, RandomProfile, PlatformMatchedProfile). For a non-Chromium profile (Firefox, Safari) it returns the same navigation headers with that profile's own User-Agent and no Client Hints, since those browsers send none. The result is never nil.
func ClientHintsHeaders ¶
ClientHintsHeaders returns sec-ch-ua-* headers for Chromium-based UAs. Returns nil for Safari/Firefox (they don't send Client Hints).
The sec-ch-ua brand list is built the way Chromium builds it (components/embedder_support/user_agent_utils.cc, GenerateBrandVersionList): a GREASE brand, "Chromium" and the browser brand ("Google Chrome", or "Microsoft Edge" for Edge), each carrying the major version, shuffled by a permutation seeded with the Chromium major. Both the GREASE brand/version and the order therefore change with the major; a fixed brand string agrees with at most one release.
func DefaultHeaderOrderFor ¶ added in v1.26.0
DefaultHeaderOrderFor returns the request-header order that matches the given BrowserProfile.Browser value: Firefox gets its own order, every other browser the Chrome order (the order for Safari has not been captured).
func ExtractChromeVersion ¶
ExtractChromeVersion extracts the major Chrome version from a User-Agent string.
func IsRetryable ¶
IsRetryable returns true for transient errors worth retrying.
func IsRetryableStatus ¶
IsRetryableStatus returns true for HTTP status codes worth retrying.
func MaskProxy ¶
MaskProxy masks credentials in a proxy URL for safe logging. "socks5://user:pass@host:1080" -> "socks5://***@host:1080"
func ParseRetryAfter ¶
ParseRetryAfter extracts the wait duration from a Retry-After header. Handles both delta-seconds ("120") and HTTP-date ("Thu, 01 Dec 2025 16:00:00 GMT") formats. Returns 0 if the header is missing or unparseable.
func RandomUserAgent ¶
func RandomUserAgent() string
RandomUserAgent returns a random Chrome-like User-Agent.
func RetryDo ¶
RetryDo retries fn up to MaxRetries times with exponential backoff. Retries only on retryable errors; returns immediately on non-retryable or context cancellation.
func RetryDoWithReset ¶
func RetryDoWithReset[T any]( ctx context.Context, rc RetryConfig, resetFn func(), fn func() (T, error), ) (T, error)
RetryDoWithReset is like RetryDo but calls resetFn before each retry attempt (not before the first attempt). If resetFn is nil, it behaves identically to RetryDo.
func RetryHTTP ¶
func RetryHTTP(ctx context.Context, rc RetryConfig, fn func() (*http.Response, error)) (*http.Response, error)
RetryHTTP executes an HTTP request function with retry logic.
func RetryHTTPWithReset ¶
func RetryHTTPWithReset( ctx context.Context, rc RetryConfig, resetFn func(), fn func() (*http.Response, error), ) (*http.Response, error)
RetryHTTPWithReset is like RetryHTTP but calls resetFn before each retry attempt. If resetFn is nil, it behaves identically to RetryHTTP.
func UserAgentForProfile ¶ added in v1.21.0
func UserAgentForProfile(p TLSProfile) string
UserAgentForProfile returns a User-Agent string from BuiltinProfiles whose TLSProfile matches p. It returns the FIRST matching entry (BuiltinProfiles holds per-OS variants sharing one TLS profile; the first is Windows where available). Returns "" when no BuiltinProfiles entry matches p — this is the documented no-entry behaviour, NOT a fallback: callers pairing a UA with an unknown profile must handle the empty string rather than silently presenting a mismatched UA. This is the single lookup the consumer repos use to delete their hardcoded UA literals.
func ValidateProxy ¶
func ValidateProxy(client *BrowserClient, testURL string) error
ValidateProxy tests proxy connectivity by fetching the given URL. Returns nil if the proxy is reachable, error otherwise.
func WithRetryHook ¶
func WithRetryHook(ctx context.Context, fn RetryHookFunc) context.Context
WithRetryHook returns a context carrying the given retry hook.
Types ¶
type AnalyzeResponse ¶
type AnalyzeResponse struct {
URL string `json:"url"`
Status int `json:"status"`
Technologies []AnalyzeTech `json:"technologies"`
Error string `json:"error,omitempty"`
}
AnalyzeResponse is the response from /analyze.
type AnalyzeTech ¶
type AnalyzeTech struct {
Name string `json:"name"`
Categories []string `json:"categories"`
Confidence int `json:"confidence"`
Version *string `json:"version,omitempty"`
}
AnalyzeTech is a single detected technology.
type BackendConfig ¶
type BackendConfig struct {
Profile TLSProfile
ProxyURL string
TimeoutSeconds int
FollowRedirects bool
HTTP3 bool
// FetchMode requests the fetch/XHR request shape: HTTP/2 priority weight
// 220 on HEADERS instead of the navigation weight 256 (see WithFetchMode).
FetchMode bool
// CookieJar, when non-nil, replaces the backend's own cookie jar (see
// WithCookieJar). nil = each backend's default jar.
CookieJar http.CookieJar
// InsecureSkipVerify, when true, disables TLS certificate verification.
// Honored by the tls-client backend; the std backend verifies by default
// (net/http) and will honor this field when wired.
InsecureSkipVerify bool
// DialControl, when non-nil, is installed as the connect-time SSRF guard
// on the backend's dialer (net.Dialer.Control on the std backend;
// tls-client WithDialer on the tls backend). It fires on the
// already-resolved address, per redirect hop, on the DIRECT path.
DialControl func(network, address string) error
// RedirectGuard, when non-nil AND FollowRedirects is true, is installed as
// the per-hop redirect check (http.Client.CheckRedirect on the std
// backend; an fhttp→stdlib-adapted WithCustomRedirectFunc on the tls
// backend). It must re-own the redirect hop cap.
RedirectGuard func(req *http.Request, via []*http.Request) error
// contains filtered or unexported fields
}
BackendConfig holds backend-agnostic configuration for creating an HTTPDoer.
type BackendFactory ¶
type BackendFactory func(cfg BackendConfig) (HTTPDoer, error)
BackendFactory creates an HTTPDoer from configuration.
type BackoffConfig ¶
type BackoffConfig struct {
InitialWait time.Duration // base delay for attempt 0
MaxWait time.Duration // ceiling
Multiplier float64 // growth factor per attempt
JitterPct float64 // random variation (0.3 = +/-30%)
}
BackoffConfig defines exponential backoff parameters with jitter.
type BrowserClient ¶
type BrowserClient struct {
// contains filtered or unexported fields
}
BrowserClient wraps an HTTPDoer backend with middleware, proxy rotation, and TLS fingerprint impersonation.
func NewClient ¶
func NewClient(opts ...ClientOption) (*BrowserClient, error)
NewClient creates a BrowserClient with the given options.
func (*BrowserClient) ChromeHeaders ¶ added in v1.26.1
func (bc *BrowserClient) ChromeHeaders() map[string]string
ChromeHeaders returns Chrome navigation headers matching the identity this client presents (see Identity): the identity's User-Agent and Client Hints over the same header set as the package-level ChromeHeaders. Never nil; a non-Chromium identity carries no Client Hints.
func (*BrowserClient) Close ¶ added in v1.22.4
func (bc *BrowserClient) Close() error
Close releases resources the client owns. Only pools the client created itself — via WithWebshareCountry or WithWebshareRotating — are closed (this stops the Webshare credential refresher); a pool supplied through WithProxyPool belongs to the caller and is left alone. Likewise the backend the client built itself (the default tls-client, or WithStdHTTP) is closed, while one supplied through WithBackend belongs to the caller and is left alone. Closing the backend releases its idle connections (and HTTP/3 sockets); a connection serving a request is left to finish, and an HTTP/2 connection used by an in-flight request can stay open until its idle timeout (about 90 s). A Do after Close opens new connections that nothing closes, and Close must not run concurrently with Do. Idempotent; returns the owned pool's first close error, if any (the backend is closed regardless).
func (*BrowserClient) CloseIdleConnections ¶ added in v1.25.0
func (bc *BrowserClient) CloseIdleConnections()
CloseIdleConnections closes the idle connections of the backend the client built itself (the default tls-client, or WithStdHTTP); a backend supplied through WithBackend belongs to the caller and is left alone. A connection in use is left alone, and one opened later idles out by itself. Unlike Close it is safe to call concurrently with Do, repeatedly, and after Close.
func (*BrowserClient) Do ¶
func (bc *BrowserClient) Do(method, urlStr string, headers map[string]string, body io.Reader) ([]byte, map[string]string, int, error)
Do executes an HTTP request with TLS fingerprint impersonation. Returns (body bytes, response headers, HTTP status code, error). If a ProxyPool was configured, each call rotates to the next proxy. Middleware added via Use() is applied to each request.
func (*BrowserClient) DoCtx ¶
func (bc *BrowserClient) DoCtx(ctx context.Context, method, urlStr string, headers map[string]string, body io.Reader) ([]byte, map[string]string, int, error)
DoCtx executes an HTTP request with context cancellation/deadline support. If ctx is already cancelled, returns ctx.Err() immediately. Otherwise runs Do in a goroutine and returns when the request completes or ctx is done.
func (*BrowserClient) DoWithHeaderOrder ¶
func (bc *BrowserClient) DoWithHeaderOrder(method, urlStr string, headers map[string]string, body io.Reader, order []string) ([]byte, map[string]string, int, error)
DoWithHeaderOrder executes a request with a custom header order. Middleware and proxy rotation are applied.
func (*BrowserClient) DoWithHeaderOrderCtx ¶
func (bc *BrowserClient) DoWithHeaderOrderCtx(ctx context.Context, method, urlStr string, headers map[string]string, body io.Reader, order []string) ([]byte, map[string]string, int, error)
DoWithHeaderOrderCtx executes a request with a custom header order and context support. If ctx is already cancelled, returns ctx.Err() immediately. Otherwise runs DoWithHeaderOrder in a goroutine and returns when the request completes or ctx is done.
func (*BrowserClient) GetCookieValue ¶
func (bc *BrowserClient) GetCookieValue(rawURL, name string) string
GetCookieValue returns the value of a named cookie for the given URL.
func (*BrowserClient) Identity ¶ added in v1.21.0
func (bc *BrowserClient) Identity() BrowserIdentity
Identity returns the BrowserIdentity the client is actually presenting: the TLS profile installed on the backend, the User-Agent paired with it, and the Client Hints derived from that User-Agent. For a client built with only WithProfile (or the bare default), the UA is resolved from BuiltinProfiles so it agrees with the JA3 by contract. For a client built with WithIdentity, the supplied identity is returned verbatim.
This is the accessor consumer repos use to obtain the User-Agent that matches the fingerprint they are presenting, instead of hardcoding their own UA literal that can drift from the library's default profile.
func (*BrowserClient) RoundTrip ¶
RoundTrip implements the http.RoundTripper interface. This allows BrowserClient to be used as a Transport for any standard net/http consumer (resty, go-retryablehttp, etc.) while maintaining TLS fingerprint impersonation.
Middleware added via Use() is applied. Proxy rotation is applied.
func (*BrowserClient) SetProxy ¶
func (bc *BrowserClient) SetProxy(proxyURL string) error
SetProxy changes the proxy URL for subsequent requests.
func (*BrowserClient) StdClient ¶
func (bc *BrowserClient) StdClient() *http.Client
StdClient returns a standard *http.Client that uses this BrowserClient as transport. The returned client preserves TLS fingerprinting, proxy rotation, and middleware.
func (*BrowserClient) Use ¶
func (bc *BrowserClient) Use(mw ...Middleware)
Use appends middlewares to the client's middleware chain. Middlewares execute in the order they are added (first added = outermost).
type BrowserIdentity ¶ added in v1.21.0
type BrowserIdentity struct {
BrowserProfile
// ClientHints holds the sec-ch-ua-* headers derived from UserAgent via
// ClientHintsHeaders. nil for Safari/Firefox (they send no Client Hints).
ClientHints map[string]string
}
BrowserIdentity is the single owner of everything a target observes about this client's browser half: the TLS fingerprint (JA3/JA4), the User-Agent, and the Client Hints (sec-ch-ua-*) derived from that User-Agent. It embeds BrowserProfile as its core so every existing caller of BuiltinProfiles, RandomProfile, PlatformMatchedProfile and session.WithProfile keeps compiling unchanged — promoted fields give id.UserAgent, id.TLSProfile, id.Browser, id.OS, id.Mobile directly, and id.BrowserProfile is reachable when the whole struct is needed.
A BrowserClient built with only a TLSProfile (the common case) resolves its Identity() from BuiltinProfiles so the UA and Client Hints agree with the JA3 by contract, not coincidence. WithIdentity sets profile and UA together so they can never drift apart.
type BrowserProfile ¶
type BrowserProfile struct {
UserAgent string
TLSProfile TLSProfile
Browser string // "chrome", "firefox", "safari", "edge"
OS string // "windows", "macos", "linux", "android", "ios"
Mobile bool
}
BrowserProfile pairs a User-Agent string with a matching TLS fingerprint and metadata for filtering.
func PlatformMatchedProfile ¶
func PlatformMatchedProfile() BrowserProfile
PlatformMatchedProfile returns a BrowserProfile whose OS matches the actual runtime platform (runtime.GOOS).
func RandomProfile ¶
func RandomProfile(opts ...ProfileOption) BrowserProfile
RandomProfile returns a random BrowserProfile matching the given filters. With no options, returns any profile.
When no profile satisfies every filter it never substitutes an unrelated browser: if a browser filter was given, the OS and mobile filters are relaxed (the browser is kept, e.g. WithBrowser("safari")+WithOS("macos") yields an iOS Safari profile); if that still matches nothing, or no browser filter was given, it returns the zero BrowserProfile, whose UserAgent is "". Callers that filter must check for that.
type ByparrConfig ¶
type ByparrConfig struct {
// BaseURL is the solver endpoint (e.g. "http://127.0.0.1:8191").
BaseURL string
// Timeout for solve requests. Default: 60s.
Timeout time.Duration
// CacheTTL for solved cookies. Default: 25 minutes (cf_clearance lifetime).
CacheTTL time.Duration
}
ByparrConfig configures the Byparr/FlareSolverr solver.
type ByparrSolver ¶
type ByparrSolver struct {
// contains filtered or unexported fields
}
ByparrSolver implements CookieProvider by calling a FlareSolverr-compatible API. Caches solved cookies per domain with TTL.
func NewByparrSolver ¶
func NewByparrSolver(cfg ByparrConfig) *ByparrSolver
NewByparrSolver creates a CookieProvider that calls Byparr/FlareSolverr.
func (*ByparrSolver) GetCookie ¶
func (s *ByparrSolver) GetCookie(domain string) string
GetCookie returns a cached cf_clearance cookie for the domain.
func (*ByparrSolver) Solve ¶
func (s *ByparrSolver) Solve(domain string, challenge *CloudflareError) (string, error)
Solve calls the Byparr/FlareSolverr API to solve a Cloudflare challenge.
type ChallengeType ¶
type ChallengeType string
ChallengeType identifies the kind of Cloudflare challenge.
const ( ChallengeJS ChallengeType = "js_challenge" ChallengeTurnstile ChallengeType = "managed_challenge" ChallengeBlock ChallengeType = "block" ChallengeManagedAt200 ChallengeType = "managed_challenge_200" )
type ClientOption ¶
type ClientOption func(*clientConfig)
ClientOption configures a BrowserClient.
func WithBackend ¶
func WithBackend(factory BackendFactory) ClientOption
WithBackend sets a custom backend factory for creating the HTTPDoer. If not set, the default bogdanfinn/tls-client backend is used.
func WithCookieJar ¶ added in v1.28.0
func WithCookieJar(jar http.CookieJar) ClientOption
WithCookieJar makes the client store and send cookies through jar instead of its own private jar. Pass the same jar to several clients to share one cookie store, as the tabs and workers of one browser do: a cookie set by a response to one client is sent by the next request of another.
The jar decides everything about a cookie: which domain and path it is scoped to, expiry and deletion (a cookie set with MaxAge < 0 is removed), and the order cookies are sent in. net/http/cookiejar orders them as RFC 6265 does and as Chrome does: longer path first, then earliest creation, so cookies from one response keep their Set-Cookie order. The caller can seed, read or delete cookies by calling the jar directly, which lets it drop an explicit Cookie header (a Cookie header set on a request is sent in addition to the jar's cookies, so a cookie in both is sent twice).
The jar must be safe for concurrent use (net/http/cookiejar is). A nil jar keeps the default per-client jar. The tls-client default jar is not changed: it keys cookies by the last two host labels, ignores Domain and Path, and returns one response's cookies in reverse Set-Cookie order. Works with both backends and does not touch the TLS or HTTP/2 wire.
func WithCookieSolver ¶
func WithCookieSolver(provider CookieProvider) ClientOption
WithCookieSolver enables Cloudflare challenge solving via a CookieProvider. Automatically adds CloudflareDetectMiddleware and CloudflareCookieMiddleware.
func WithDebug ¶
func WithDebug() ClientOption
WithDebug enables request/response logging via slog.Debug. Automatically adds LoggingMiddleware to the middleware chain.
func WithDialControl ¶
func WithDialControl(fn func(network, address string) error) ClientOption
WithDialControl overrides the connect-time (tier-1) SSRF guard: the rebind-proof check run on the already-resolved address immediately before connect(2), on both backends' dialers. Pass go-kit/httputil's SSRFGuards() dial closure (or DenyBlockedAddress) here for the framework's full policy. A nil fn disables the connect-time guard.
On a PROXIED client this hook sees only the proxy's address, never the real target — pair it with WithRequestURLGuard (tier 3), which does. The client passes the configured proxy's own endpoint (its resolved addresses and port, following SetProxy) without consulting fn, so a proxy on a private address stays reachable; every other address still goes through fn. The consequence: a stricter caller guard is bypassed for those proxy endpoints (every proxy set on this client recently, at most the last 64 distinct URLs), because they are operator configuration.
func WithFetchMode ¶ added in v1.27.0
func WithFetchMode() ClientOption
WithFetchMode makes the client behave like a Chrome page issuing fetch() and XMLHttpRequest calls rather than top-level navigations (the default). Two things change, and both match what Chrome sends for such requests:
- HTTP/2: the HEADERS frame carries exclusive priority weight 220 instead of the navigation weight 256.
- Headers: a request without its own "priority" header gets "priority: u=1, i". A priority header the caller sets is kept as is.
The mode belongs to the whole client because HTTP/2 priority is a property of the connection's transport, not of a single request; a caller that mixes navigations and fetches builds one client of each (they then use separate connections, which Chrome does not). The cookie jar is per client.
It needs a Chrome-family TLS profile (chrome_*, brave_*) and a Chromium identity (chrome, edge, brave); on Firefox or Safari, or an identity that names one of them, NewClient returns ErrFetchModeUnsupported rather than present a Chrome request shape over another browser's fingerprint. It is opt-in for every profile, chrome_131 included: without it the wire is unchanged. With WithStdHTTP only the header part applies (net/http cannot set a priority).
func WithFollowRedirects ¶
func WithFollowRedirects() ClientOption
WithFollowRedirects enables redirect following (disabled by default).
func WithHTTP3 ¶
func WithHTTP3() ClientOption
WithHTTP3 enables HTTP/3 QUIC support (tls-client backend only).
func WithHeaderOrder ¶
func WithHeaderOrder(order []string) ClientOption
WithHeaderOrder sets the default HTTP header ordering for requests.
func WithIdentity ¶ added in v1.21.0
func WithIdentity(id BrowserIdentity) ClientOption
WithIdentity sets the browser identity — TLS profile and User-Agent (plus Client Hints) — together, so they can never be configured apart and drift. The identity's TLSProfile is installed as the backend's fingerprint, and Identity() returns the supplied identity verbatim. If ClientHints is nil for a Chromium UA, it is derived from the User-Agent via ClientHintsHeaders so the identity the client reports is always complete.
WithIdentity and WithProfile may both be passed; WithIdentity wins for the profile (it sets c.profile too) and supplies the UA, which WithProfile alone cannot do. Use WithIdentity when the caller already holds a BrowserProfile from RandomProfile/PlatformMatchedProfile and wants the client's reported identity to match exactly.
func WithInsecureSkipVerify ¶
func WithInsecureSkipVerify() ClientOption
WithInsecureSkipVerify disables TLS certificate verification. WARNING: this makes connections vulnerable to man-in-the-middle attacks. Use only for local testing (httptest.NewTLSServer) or explicit MITM proxy inspection. Never use in production against public endpoints.
func WithOxBrowser ¶
func WithOxBrowser(url string) ClientOption
WithOxBrowser enables ox-browser integration for CF solving and smart fetch. If no CookieProvider is already set, adds OxBrowserSolver + CloudflareDetectMiddleware. Always adds SmartFetchMiddleware as a fallback for CF-challenged responses. url is the ox-browser base URL (e.g. "http://127.0.0.1:8901"). Calls to ox-browser carry X-Internal-Secret from INTERNAL_SERVICE_SECRET when set — unless a proxy pool routes them through an external proxy, where the header would transit the proxy and is not sent.
func WithProfile ¶
func WithProfile(p TLSProfile) ClientOption
WithProfile sets the TLS client profile for fingerprint impersonation. The zero value "" means "no preference" and keeps the default profile; a non-empty name with no backend mapping makes NewClient fail with ErrUnknownTLSProfile.
func WithProxy ¶
func WithProxy(url string) ClientOption
WithProxy sets the proxy URL (e.g. "socks5://user:pass@host:port").
func WithProxyPool ¶
func WithProxyPool(pool ProxyPoolProvider) ClientOption
WithProxyPool enables per-request proxy rotation. Each call to Do() will cycle to the next proxy in the pool.
func WithRedirectGuard ¶
WithRedirectGuard overrides the per-hop (tier-2) SSRF guard: a CheckRedirect-shaped closure run on every redirect hop the backend follows (only relevant with WithFollowRedirects). It MUST re-own a hop cap — a custom CheckRedirect replaces net/http's built-in 10-hop limit. Pass go-kit/httputil's SSRFGuards() redirect closure here. A nil fn disables the per-hop guard (the backend then falls back to its built-in redirect cap).
On the std backend, req and via are the exact *http.Request values net/http's own redirect loop uses (Body/Cancel/ctx and all). On the tls backend, req and via are adapted from bogdanfinn/fhttp's redirect chain: Method, URL, and Header are populated faithfully hop-by-hop (fhttp builds its own via chain from real prior requests, same shape as net/http's), but Body/GetBody/Cancel/Context and any other *http.Request field are left at their zero value — a guard that needs only len(via)/via[i].URL/via[i].Host (the go-kit SSRFGuards() shape) works unchanged on both backends; a guard reading Body or Context should not assume either backend populates them.
func WithRequestURLGuard ¶
WithRequestURLGuard overrides the pre-request (tier-3) SSRF guard: a check on the INITIAL target URL, evaluated before the (possibly proxied) fetch. This is the ONLY tier that guards a proxied fetch's initial target, since a proxied dial control (tier 1) sees only the proxy. Signature matches go-kit/httputil.CheckURL, so a consumer can wire it directly. A nil fn disables the pre-request guard.
func WithRetryOnBlock ¶
func WithRetryOnBlock(n int) ClientOption
WithRetryOnBlock enables automatic retry with proxy rotation when the server returns a block status (403, 429). Each retry uses the next proxy from the pool. Requires WithProxyPool. n is the number of extra retries (total attempts = 1 + n).
func WithStdHTTP ¶
func WithStdHTTP() ClientOption
WithStdHTTP uses the standard net/http backend instead of tls-client. No TLS fingerprinting — useful for testing and CGO-free environments.
func WithTimeout ¶
func WithTimeout(seconds int) ClientOption
WithTimeout sets the request timeout in seconds.
func WithWebshareCountry ¶
func WithWebshareCountry(apiKey string, countries ...string) ClientOption
WithWebshareCountry creates a Webshare backbone proxy pool targeting the given ISO-2 country codes (e.g. "US", "GB"). Defaults to ["US"] if no countries are given. The API call is made eagerly; if it fails, NewClient returns the error.
The client owns the pool this option builds: call BrowserClient.Close to stop the pool's periodic credential refresher when the client is done.
func WithWebshareRotating ¶
func WithWebshareRotating(username, password string, countries ...string) ClientOption
WithWebshareRotating creates a Webshare rotating-endpoint proxy pool without calling the Webshare API. Uses username-CC-rotate syntax. Defaults to ["US"] when no countries are given.
The client owns the pool this option builds (BrowserClient.Close releases it); a rotating pool runs no refresher, so closing it is a no-op today.
func WithoutSSRFGuard ¶
func WithoutSSRFGuard() ClientOption
WithoutSSRFGuard clears all three SSRF guard tiers (dial, redirect, pre-request), restoring the pre-guard byte-for-byte behavior.
FOR TESTS ONLY. A BrowserClient is fail-closed by construction — its built-in stdlib default-deny refuses loopback/private/link-local targets, which is exactly what an httptest (127.0.0.1) suite needs to opt out of. Never call this from production code; the fleet fitness function forbids it outside _test.go.
type CloudflareError ¶
type CloudflareError struct {
Type ChallengeType
StatusCode int
RayID string
}
CloudflareError indicates a Cloudflare challenge or block was detected.
func DetectCloudflare ¶
func DetectCloudflare(resp *Response) *CloudflareError
DetectCloudflare inspects a Response for Cloudflare challenge markers. Returns nil if the response is not a Cloudflare challenge.
Signal precedence (under the server~cloudflare gate):
- Block markers (any status) — a block page that also mentions challenge-platform or carries cf-mitigated must stay a block, not be promoted to a solvable challenge.
- cf-mitigated: challenge header (any status) — the documented, status-independent challenge-page signal. Top precedence among challenge signals.
- Body markers (any status): turnstile, then challenge-platform/_cf_chl_opt.
Status is a kind hint, not a gate: 200 → ChallengeManagedAt200, otherwise ChallengeJS. The fix is additive (recognise more challenges), never promotive (a 403 WAF block carrying neither cf-mitigated nor block/body markers returns nil — promoting unknown 403s would burn solver time on hard blocks).
Cloudflare moved the JS/managed-challenge status from 503 to 403 on 2023-03-01; cf-mitigated: challenge is set on all challenge-page types regardless of status and is the only valid value for that header.
- https://developers.cloudflare.com/cloudflare-challenges/challenge-types/challenge-pages/detect-response/
- https://community.cloudflare.com/t/community-update-status-code-for-javascript-challenge-changing-from-503-to-403/445724
- https://developers.cloudflare.com/rules/custom-errors/reference/error-page-types/
func (*CloudflareError) Error ¶
func (e *CloudflareError) Error() string
type CookieProvider ¶
type CookieProvider interface {
// GetCookie returns a cached cf_clearance cookie for the domain.
// Returns empty string if no cached cookie is available.
GetCookie(domain string) string
// Solve attempts to solve a Cloudflare challenge and returns the cookie string.
// The cookie string should be in "cf_clearance=value" format.
Solve(domain string, challenge *CloudflareError) (string, error)
}
CookieProvider obtains Cloudflare clearance cookies from an external source.
type FetchSmartResponse ¶
type FetchSmartResponse struct {
Status int `json:"status"`
Body string `json:"body"`
Method string `json:"method"`
CfDetected bool `json:"cf_detected"`
ElapsedMs int64 `json:"elapsed_ms"`
Error string `json:"error,omitempty"`
}
FetchSmartResponse is the response from /fetch-smart.
type HTTPDoer ¶
type HTTPDoer interface {
Do(req *Request) (*Response, error)
SetProxy(url string) error
GetCookieValue(rawURL, name string) string
}
HTTPDoer executes HTTP requests. TLS backends implement this interface.
type Handler ¶
Handler processes a Request and returns a Response.
func ClientHintsMiddleware ¶
ClientHintsMiddleware auto-injects sec-ch-ua-* headers for Chromium-based profiles. This ensures Client Hints match the User-Agent, preventing fingerprint mismatch detection.
func CloudflareDetectMiddleware ¶
CloudflareDetectMiddleware inspects responses for Cloudflare challenges. If a challenge is detected, it returns a *CloudflareError (use errors.As to extract). Non-challenge responses pass through unchanged.
func LoggingMiddleware ¶
LoggingMiddleware logs each request's method, URL, status, and latency via slog.
type HttpStatusError ¶
type HttpStatusError struct {
StatusCode int
}
HttpStatusError wraps a retryable HTTP status code. Exported so consumers can use stealth.RetryDo/RetryHTTP directly without reimplementing their own error type for errors.As matching.
func (*HttpStatusError) Error ¶
func (e *HttpStatusError) Error() string
type Jitter ¶
Jitter is an alias for pacing.Jitter, preserving the stealth-package API for existing callers (go-twitter vendor). New code should import pacing directly.
type Middleware ¶
Middleware wraps a Handler to add behavior before/after request execution.
func Chain ¶
func Chain(middlewares ...Middleware) Middleware
Chain composes multiple middlewares into a single Middleware. Middlewares are applied in order: first middleware wraps the outermost layer.
Chain(logging, retry, rateLimit)(handler) // execution order: logging → retry → rateLimit → handler
func CloudflareCookieMiddleware ¶
func CloudflareCookieMiddleware(provider CookieProvider) Middleware
CloudflareCookieMiddleware returns a middleware that:
- Injects cached cf_clearance cookies from the provider before each request.
- On Cloudflare challenge response, calls provider.Solve() to get a cookie and retries once.
func RateLimitMiddleware ¶
func RateLimitMiddleware(limiter *ratelimit.DomainLimiter) Middleware
RateLimitMiddleware returns a middleware that enforces per-domain rate limits. Blocks until the request is allowed or context deadline is exceeded.
func RateLimitMiddlewareWithContext ¶
func RateLimitMiddlewareWithContext(ctx context.Context, limiter *ratelimit.DomainLimiter) Middleware
RateLimitMiddlewareWithContext returns a rate limit middleware that respects context cancellation.
func RetryMiddleware ¶
func RetryMiddleware(cfg RetryConfig) Middleware
RetryMiddleware returns a middleware that retries failed requests using the given config.
func RetryMiddlewareWithContext ¶
func RetryMiddlewareWithContext(ctx context.Context, cfg RetryConfig) Middleware
RetryMiddlewareWithContext returns a retry middleware that respects context cancellation.
func SmartFetchMiddleware ¶
func SmartFetchMiddleware(oxClient *OxBrowserClient) Middleware
SmartFetchMiddleware returns a middleware that detects Cloudflare challenges and delegates to ox-browser /fetch-smart for automatic solving. On CF detection, the original response is replaced with ox-browser's solved response. Non-CF responses pass through unchanged.
type OxBrowserClient ¶
type OxBrowserClient struct {
// contains filtered or unexported fields
}
OxBrowserClient calls the ox-browser REST API.
func NewOxBrowserClient ¶
func NewOxBrowserClient(baseURL string) *OxBrowserClient
NewOxBrowserClient creates a client for ox-browser at the given base URL. Does NOT route through proxy. Use NewOxBrowserClientWithProxy for stealth scenarios. Requests to ox-browser carry the X-Internal-Secret header from INTERNAL_SERVICE_SECRET when that env var is set (go-kit svcauth); the header is scoped to baseURL's origin and stripped on requests and redirect hops to anywhere else.
func NewOxBrowserClientWithProxy ¶
func NewOxBrowserClientWithProxy(baseURL string, proxyFn func(*http.Request) (*url.URL, error)) *OxBrowserClient
NewOxBrowserClientWithProxy creates a client for ox-browser that routes all requests (to the ox-browser API itself) through the given proxy function. proxyFn is compatible with http.Transport.Proxy — pass proxyPool.TransportProxy() to ensure each call to ox-browser uses a fresh rotated residential IP.
No X-Internal-Secret is attached on this path, by design: a plain-http request to ox-browser transits the external proxy in cleartext, so the secret would be visible to the proxy operator.
func (*OxBrowserClient) Analyze ¶
func (c *OxBrowserClient) Analyze(ctx context.Context, url string) (*AnalyzeResponse, error)
Analyze calls ox-browser /analyze for tech detection.
func (*OxBrowserClient) FetchSmart ¶
func (c *OxBrowserClient) FetchSmart(ctx context.Context, url string) (*FetchSmartResponse, error)
FetchSmart calls ox-browser /fetch-smart (auto CF bypass).
type OxBrowserSolver ¶
type OxBrowserSolver struct {
// contains filtered or unexported fields
}
OxBrowserSolver implements CookieProvider using ox-browser /solve. Drop-in replacement for ByparrSolver.
func NewOxBrowserSolver ¶
func NewOxBrowserSolver(cfg OxBrowserSolverConfig) *OxBrowserSolver
NewOxBrowserSolver creates a CookieProvider backed by ox-browser. Its calls to ox-browser carry X-Internal-Secret from INTERNAL_SERVICE_SECRET when set — unless cfg.ProxyFn routes them through an external proxy (see NewOxBrowserClientWithProxy).
func (*OxBrowserSolver) GetCookie ¶
func (s *OxBrowserSolver) GetCookie(domain string) string
GetCookie returns a cached cf_clearance cookie for the domain.
func (*OxBrowserSolver) Solve ¶
func (s *OxBrowserSolver) Solve(domain string, challenge *CloudflareError) (string, error)
Solve calls ox-browser /solve to obtain CF clearance cookies.
type OxBrowserSolverConfig ¶
type OxBrowserSolverConfig struct {
// BaseURL of ox-browser (e.g. "http://127.0.0.1:8901").
BaseURL string
// CacheTTL for solved cookies. Default: 25 minutes.
CacheTTL time.Duration
// ProxyFn, if non-nil, routes requests to ox-browser through a proxy.
// Compatible with http.Transport.Proxy — pass proxyPool.TransportProxy().
// With ProxyFn set, no X-Internal-Secret is sent to ox-browser: the
// request itself transits the external proxy, which would see the header.
ProxyFn func(*http.Request) (*url.URL, error)
}
OxBrowserSolverConfig configures the ox-browser CF solver.
type ProfileOption ¶
type ProfileOption func(*profileFilter)
ProfileOption configures profile filtering for RandomProfile.
func WithBrowser ¶
func WithBrowser(b string) ProfileOption
WithBrowser filters profiles by browser. Valid values: "chrome", "firefox", "safari", "edge".
func WithMobile ¶
func WithMobile(mobile bool) ProfileOption
WithMobile filters for mobile or desktop profiles.
func WithOS ¶
func WithOS(os string) ProfileOption
WithOS filters profiles by operating system. Valid values: "windows", "macos", "linux", "android", "ios".
type ProxyPoolProvider ¶
type ProxyPoolProvider interface {
Next() string
}
ProxyPoolProvider returns the next proxy URL for rotation.
type Request ¶
type Request struct {
Method string
URL string
Headers map[string]string
Body io.Reader
HeaderOrder []string // backend uses this for header ordering
}
Request represents an outgoing HTTP request for the middleware chain.
type RetryConfig ¶
type RetryConfig struct {
MaxRetries int
InitialWait time.Duration
MaxWait time.Duration
Multiplier float64
JitterPct float64 // random variation (0 = no jitter)
}
RetryConfig controls retry behavior.
type RetryHookFunc ¶
RetryHookFunc is called before each retry attempt. attempt is 1-based; err is the error from the previous attempt.
type RetryTracker ¶
type RetryTracker struct {
// contains filtered or unexported fields
}
RetryTracker tracks per-URL retry state across calls. Thread-safe. Entries expire after TTL via lazy eviction.
func NewRetryTracker ¶
func NewRetryTracker(maxAttempts int, ttl time.Duration) *RetryTracker
NewRetryTracker creates a tracker that allows up to maxAttempts per URL before blocking retries. Entries expire after ttl (lazy eviction).
func (*RetryTracker) RecordAttempt ¶
func (t *RetryTracker) RecordAttempt(url string, err error)
RecordAttempt records a failed attempt for the URL. If the error is a permanent HTTP status (4xx client errors), the URL is marked as permanently failed and no further retries are allowed regardless of attempt count.
func (*RetryTracker) RecordSuccess ¶
func (t *RetryTracker) RecordSuccess(url string)
RecordSuccess clears all retry state for the URL, making it fully retryable again.
func (*RetryTracker) ShouldRetry ¶
func (t *RetryTracker) ShouldRetry(url string) bool
ShouldRetry returns true if the URL is eligible for another attempt. Unknown URLs are always retryable. Expired entries are evicted lazily.
type SiteInfo ¶
type SiteInfo struct {
WAF string // Detected WAF/CDN name, or ""
Technologies []AnalyzeTech // Full tech list
FetchedAt time.Time
}
SiteInfo contains cached intelligence about a domain.
type SiteIntel ¶
type SiteIntel struct {
// contains filtered or unexported fields
}
SiteIntel provides per-domain tech/WAF intelligence with caching.
func NewSiteIntel ¶
func NewSiteIntel(client *OxBrowserClient) *SiteIntel
NewSiteIntel creates a SiteIntel cache backed by ox-browser /analyze.
func (*SiteIntel) Get ¶
Get returns cached SiteInfo for the domain, fetching via /analyze if needed. Concurrent calls for the same domain are deduplicated via singleflight.
func (*SiteIntel) SuggestProfile ¶
func (si *SiteIntel) SuggestProfile(rawURL string) BrowserProfile
SuggestProfile returns a BrowserProfile optimized for the target site's WAF.
type SolveResponse ¶
type SolveResponse struct {
Status string `json:"status"`
Cookies map[string]string `json:"cookies"`
Error string `json:"error,omitempty"`
}
SolveResponse is the response from /solve.
type TLSProfile ¶
type TLSProfile string
TLSProfile identifies a TLS fingerprint profile.
const ( ProfileChrome131 TLSProfile = "chrome_131" ProfileChrome133 TLSProfile = "chrome_133" ProfileChrome144 TLSProfile = "chrome_144" ProfileChrome146 TLSProfile = "chrome_146" ProfileChrome150 TLSProfile = "chrome_150" ProfileChrome152 TLSProfile = "chrome_152" ProfileChrome156 TLSProfile = "chrome_156" ProfileFirefox133 TLSProfile = "firefox_133" ProfileFirefox148 TLSProfile = "firefox_148" ProfileBrave146 TLSProfile = "brave_146" ProfileSafari16 TLSProfile = "safari_16_0" ProfileSafariIOS18 TLSProfile = "safari_ios_18_0" ProfileSafariIOS17 TLSProfile = "safari_ios_17_0" ProfileSafariIOS26 TLSProfile = "safari_ios_26_0" )
Built-in TLS profiles.
Source Files
¶
- backend.go
- backend_std.go
- backend_tlsclient.go
- backoff.go
- client.go
- client_ctx.go
- client_options.go
- client_options_webshare.go
- cloudflare.go
- decompress.go
- extension_order.go
- header_order.go
- jitter.go
- middleware.go
- middleware_builtin.go
- middleware_oxbrowser.go
- oxbrowser.go
- profile.go
- proxy.go
- proxy_allow.go
- retry.go
- retry_hook.go
- retry_reset.go
- retry_tracker.go
- roundtripper.go
- site_intel.go
- solver_byparr.go
- solver_oxbrowser.go
- ssrf.go
- ua.go
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
fingerprint-capture
command
Command fingerprint-capture downloads a Chrome-for-Testing build matching a given major version, runs it against the peet.ws and browserleaks echo endpoints, and writes the observed fingerprint to testdata/reference_chrome_<major>.json with full per-metric provenance.
|
Command fingerprint-capture downloads a Chrome-for-Testing build matching a given major version, runs it against the peet.ws and browserleaks echo endpoints, and writes the observed fingerprint to testdata/reference_chrome_<major>.json with full per-metric provenance. |
|
internal
|
|
|
fingerprint
Package fingerprint holds the reference-capture and oracle infrastructure that measures whether go-stealth's emitted TLS/HTTP2 fingerprint matches a real browser's.
|
Package fingerprint holds the reference-capture and oracle infrastructure that measures whether go-stealth's emitted TLS/HTTP2 fingerprint matches a real browser's. |
|
uri
Package uri provides shared URL utility functions.
|
Package uri provides shared URL utility functions. |
|
Package ratelimit provides token bucket rate limiters and per-key pacing.
|
Package ratelimit provides token bucket rate limiters and per-key pacing. |