curl_impersonate
Ruby bindings for libcurl-impersonate — generate real-browser TLS / JA3 / JA4 fingerprints from Ruby.
Unlike pure-Ruby HTTP libraries (net/http, httpx, faraday, …) whose TLS handshakes are unmistakably "not a browser," this gem links the same BoringSSL-backed libcurl-impersonate build that the curl-impersonate project ships, so the TLS ClientHello, HTTP/2 SETTINGS frame, and header ordering are byte-identical to Chrome, Firefox, or Safari.
This is a Ruby port of go-curl-impersonate. The public API matches it 1:1 within Ruby idioms.
Installation
# Gemfile
gem "curl_impersonate"
On platforms with a precompiled gem (arm64-darwin, x86_64-linux-gnu), gem install is a single step with no external dependencies — the BoringSSL build is statically linked into the gem's native extension.
On other platforms you will need to build from source; see Building from source.
Quick start
require "curl_impersonate"
resp = CurlImpersonate.do_request(
url: "https://example.com",
impersonate: "chrome131",
headers: { "Accept-Language" => "en-US" },
timeout_sec: 15,
)
resp.status_code # => 200
resp.success? # => true
resp.body # => "<!doctype html>..."
resp.headers # => "HTTP/2 200\r\ncontent-type: text/html\r\n..."
CurlImpersonate.(resp.headers) # => { "name" => "value", ... }
API
CurlImpersonate.do_request(...) → Response
| keyword | type | default | notes |
|---|---|---|---|
url: |
String |
— | Required. |
impersonate: |
String |
"chrome131" |
Any target supported by libcurl-impersonate. Examples: chrome131, firefox133, safari180. See the upstream targets. |
headers: |
Hash<String, String> |
{} |
Custom headers merged on top of the browser default set. |
post_data: |
String |
"" |
Non-empty switches the request to POST and is sent as the body verbatim. |
follow_redirects: |
Boolean |
true |
Maps to CURLOPT_FOLLOWLOCATION. |
timeout_sec: |
Integer |
15 |
Total request timeout. |
proxy: |
String |
"" |
"scheme://user:pass@host:port". Credentials are split out and passed via CURLOPT_PROXYUSERPWD. |
Returns a CurlImpersonate::Response Struct with status_code (Integer), body (String), and headers (String — raw \r\n-separated lines). On any libcurl error (resolution, timeout, TLS handshake, …) raises CurlImpersonate::Error.
curl_easy_perform runs without the GVL, so other Ruby threads continue executing during the network wait.
CurlImpersonate.extract_cookies(headers_str) → Hash
Parses Set-Cookie: lines from a raw header string and returns a Hash<String, String> of cookie names → values. Attributes (Path, Domain, HttpOnly, Expires, Secure, …) are discarded. Later cookies with the same name overwrite earlier ones.
Building from source
You'll need:
- A C compiler (
clang/gcc) andmake libcurl-impersonate1.5.x installed via one of:- macOS:
brew install teammilestone/tap/libcurl-impersonate - Linux: download the matching tarball from lexiforest/curl-impersonate releases and copy
libcurl-impersonate.aplus headers into a system path discoverable bypkg-config - Or set
CURL_IMPERSONATE_DIR=/path/to/install(containinglib/libcurl-impersonate.aandinclude/) beforegem install
- macOS:
gem install curl_impersonate --platform=ruby # forces source compilation
Caveats
- SSL certificate verification is disabled (
CURLOPT_SSL_VERIFYPEER = 0,CURLOPT_SSL_VERIFYHOST = 0). This matches the Go reference implementation. The premise of TLS impersonation is that you are mimicking a browser TLS stack regardless of trust chain validation; if you need real verification, this gem is the wrong tool. - JA3 hash varies between requests. Chrome injects random GREASE values into the cipher and extension lists on every TLS handshake, so the JA3 string and its hash change each time. This is the correct Chrome behavior — match on JA4 (which sorts before hashing) if you need a stable fingerprint to assert against.
- Status of this gem: under active development; API is not stable until 1.0.
License
MIT — see LICENSE.