Launch like Chrome. Stay undetected.
Playwright in. Chrome 151 out. Built by HeadlessX Labs.
Sites see Google Chrome 151. You see Hexium Browser — a C++ persona compiled into Chromium, persistent disk profiles, humanize, and GeoIP locale. No JS injectors.
|
ProxyLane — Clean residential proxies for your Hexium profiles. 28M real P2P IPs, pay only for the traffic you use, and it never expires.
Why ProxyLane for Hexium?
HEXIUM35 — 35% off any Residential package Try ProxyLane now |
|
Headless Google search, new profile — examples/linux-native/google_search_human_headless_new_profile.py
Headed linux-native Allegro cookie consent — examples/linux-native/allegro_pl_headed_http_proxy_ephemeral_random_profile.py
- Playwright
launch()— headed and--headless=new - Linux x86_64 binary in this alpha (Windows and macOS not supported yet)
- Persistent disk profiles (real tabs, not Incognito)
- C++ persona presets — Linux default
linux-native; see Personas - Seeded fingerprint sample (
fingerprint=or auto--hexium-seed=) — not a new GPU every page - Humanized mouse, keys, and scroll (
humanize=True) - GeoIP timezone, locale, and WebRTC IP from the egress IP (on by default)
- Chrome 151 web identity (User-Agent and related surface)
Depth lives in docs/. This README is the quickstart.
from hexium_browser import launch
browser = launch(headless=True) # new ~/.hexium/profiles/hexium-session-* + new fingerprint
browser = launch(
profile="Work",
headless=True,
proxy="http://user:pass@host:port",
humanize=True,
fingerprint="account-1",
) # sticky ~/.hexium/profiles/Workbrowser.new_page() opens a tab. Bare launch() is a new visitor each time; use profile="Work" or HEXIUM_USER_DATA_DIR to return to the same identity.
python examples/linux-chrome/linux_chrome_persona.py
python examples/windows-chrome/windows_chrome_persona.py --headed
python examples/linux-native/stealth_test.py
python examples/linux-native/open_google.py
python examples/linux-native/test_headless.py
python examples/windows-chrome/test_headless.pypip install hexium-browser
pip install 'hexium-browser[geoip]' # timezone / locale / WebRTC from egress IP
hexium-browser fetch # Chrome 151 binary (Linux x86_64)From this repo (if PyPI is not what you want):
pip install 'hexium-browser[geoip] @ git+https://github.com/HeadlessXLabs/hexium-browser.git@v0.1.1'
hexium-browser fetchRequires Python 3.9+ and the Playwright driver (ships with the playwright package). Do not playwright install chromium — Hexium launches its own Chrome 151 binary.
GeoIP is on by default; without the extra, launch continues and timezone stays the sampled UTC.
Linux x86_64 only (linux/amd64). The image already has hexium-browser[geoip] and the Chrome 151 binary. The entrypoint starts Xvfb :99 so headed launch(headless=False) works without a monitor.
docker pull saifyxpro/hexium-browser:0.1.1
# or: docker pull saifyxpro/hexium-browser:latest
docker run --rm saifyxpro/hexium-browser:0.1.1 hexium-browser info --quick
docker run --rm saifyxpro/hexium-browser:0.1.1 python -c \
"from hexium_browser import launch; b=launch(headless=True); p=b.new_page(); p.goto('https://example.com'); b.close()"Keep a named profile on the host:
mkdir -p ~/.hexium/profiles
docker run --rm \
-v ~/.hexium/profiles:/root/.hexium/profiles \
saifyxpro/hexium-browser:0.1.1 \
python -c "from hexium_browser import launch; b=launch(profile='Work', headless=True); b.close()"Build locally from this repo:
docker build -t saifyxpro/hexium-browser .hexium-browser fetch # download the engine (API, then GitHub Releases)
hexium-browser install # alias for fetch
hexium-browser info # wrapper + binary diagnostics
hexium-browser info --quick # skip launching chrome --version
hexium-browser info --json
hexium-browser info --proxy socks5://user:pass@host:1080
hexium-browser doctor # alias for info
hexium-browser clear-cache # delete ~/.hexium cached binaries
hexium-browser profiles # list named profiles
hexium-browser profiles list
hexium-browser profiles new Work
hexium-browser profiles use Work
hexium-browser profiles lastHexium is a real Chrome user-data-dir, not Incognito. Bare launch() creates a new ~/.hexium/profiles/hexium-session-* and a new fingerprint. Named profiles stick. profiles use only records the last name for the CLI; launch() still needs profile="Work" (or HEXIUM_USER_DATA_DIR) to reopen it.
from hexium_browser import launch
launch() # new identity
launch(profile="Work") # returning visitorThe sampled persona sticks on a named profile until you pass a new fingerprint= or use another profile=.
Migrating from Playwright? One-line change:
- from playwright.sync_api import sync_playwright
- pw = sync_playwright().start()
- browser = pw.chromium.launch()
+ from hexium_browser import launch
+ browser = launch()
page = browser.new_page()
page.goto("https://example.com")
# ... rest of your code works unchangedpage.goto(), click(), fill(), locator(), and browser.close() are the same Playwright objects. Do not pass executable_path or channel. browser.new_page() is a tab on a persistent disk profile (not Incognito). If you used browser.new_context(), switch to launch(profile="Work") or keep calling new_page().
- JS stealth injectors break — they patch
navigatorin page JS. Detection sites look for the patch. Hexium does not inject stealth scripts. - Persona is compiled in — GPU, screen, UA, and hardware reporting follow a sampled Chrome persona at the C++ layer, plus a persistent profile on disk.
- Same Playwright API —
launch(),new_page(),click(),fill(). Swap the import. - Humanize is a flag — Bézier mouse, per-character typing, realistic scroll. Optional
show_cursor=Truefor headed debug demos only.
Hexium does not solve CAPTCHAs. Bring your own proxy. Use the Playwright API you already know.
Captured 12–14 Sep 2026 against live oracles with Chrome 151. These are screenshots, not a guarantee that every site will score the same.
Shop / cookie-consent capture (headed linux-native, ephemeral session, HTTP proxy placeholders): Allegro “Dbamy o Twoją prywatność” — examples/linux-native/allegro_pl_headed_http_proxy_ephemeral_random_profile.py.
Headed Linux native — Allegro consent. Use headed + ephemeral + your own proxy for shops; do not run this flow headless.
Headless walk (examples/linux-native/test_headless.py, examples/windows-chrome/test_headless.py): headless-detector.vercel.app then Infosimples detect-headless.
| Oracle | What the capture shows |
|---|---|
Headless detector (Linux, linux-native) |
0.10 — Normal Browser |
Headless detector (Win32, windows-chrome) |
0.00 — Normal Browser |
| Infosimples detect-headless (both) | WebDriver missing, Time Elapse passes. Yellow “Broken Image” is that page’s check, not Hexium. |
| reCAPTCHA v3 demo | score 0.9 |
| BrowserScan bot detection | Normal |
| deviceandbrowserinfo.com | You are human! (isBot: false) |
| bot.sannysoft.com | WebDriver missing, window.chrome present, plugins 5, UA Chrome/151 |
| Rebrowser bot detector | no webdriver / no __pwInitScripts (some checks need a click to fire) |
![]() |
![]() |
|---|---|
Linux linux-native — headless detector 0.10, Normal Browser |
Linux — Infosimples (Time Elapse pass) |
![]() |
![]() |
|---|---|
windows-chrome — headless detector 0.00, Normal Browser |
windows-chrome — Infosimples (Time Elapse pass) |
![]() |
![]() |
|---|---|
| reCAPTCHA v3 demo — score 0.9 | BrowserScan bot detection — Normal |
![]() |
![]() |
|---|---|
| deviceandbrowserinfo.com — “You are human!” | bot.sannysoft.com — WebDriver missing, Chrome present, plugins 5 |
![]() |
![]() |
|---|---|
| Rebrowser bot detector — no webdriver, no Playwright init scripts | bot.incolumitas.com — examples/linux-native/stealth_test.py |
whoer.net — headed, ephemeral random profile, rotating HTTP proxy (HEXIUM_PROXY). Captures from examples/*/whoer_headed_http_proxy_ephemeral_random_profile.py.
![]() |
![]() |
![]() |
|---|---|---|
linux-native |
windows-chrome |
linux-chrome |
CreepJS
![]() |
![]() |
|---|---|
CreepJS (noise=false) — examples/linux-native/stealth_test.py |
CreepJS — examples/linux-native/fingerprint_scan_test.py |
Stealth/oracle examples pass humanize=True and leave show_cursor off (default). Pass show_cursor=True only in headed debug demos such as humanize_click_demo.py.
launch() defaults to humanize=True and show_cursor=False. Pass show_cursor=True in humanize_click_demo.py to see the blue debug ring.
from hexium_browser import launch
browser = launch(headless=False, humanize=True)
page = browser.new_page()
page.goto("https://example.com")
page.locator("#email").fill("user@example.com")
page.locator("button[type=submit]").click()| Interaction | Playwright default | humanize=True |
|---|---|---|
| Mouse | Instant teleport | Bézier curve, easing, slight overshoot |
| Clicks | Instant | Aim point + hold |
| Keyboard | Instant fill | Per-character timing |
| Scroll | Jump | Accelerate → cruise → decelerate |
| Humanize click demo | Still |
|---|---|
![]() |
Same demo — virtual highlighter mid-path |
| Headed Google search | Still |
|---|---|
![]() |
After the headed search — “Nextjs Latest version” |
| DABI interaction oracle | Still |
|---|---|
![]() |
Same interaction run — still frame |
| Headless, reused profile | Headless, fresh profile |
|---|---|
![]() |
![]() |
launch(persona=…) or HEXIUM_PERSONA. Linux default is linux-native. Windows default is windows-native. macOS default is macos-native (alias mac-native). Pass windows-chrome or linux-chrome to sample. A *-native name that does not match this OS remaps to the host native. The engine binary alone uses the same host defaults.
| Preset | What sites see | GPU / fonts | When |
|---|---|---|---|
linux-native |
This machine | Host GPU, fonts, screen, CPU | Linux default. |
windows-native |
This Windows machine | Host GPU, fonts, screen, CPU | Windows default. |
macos-native |
This macOS machine | Host GPU/fonts | macOS default. Alias mac-native. No macos-chrome. |
linux-chrome |
Linux x86_64 + Chrome 151 | Sampled screen/CPU/RAM; GPU/WebGL and fonts stay host (no Mesa/font jail) | persona="linux-chrome" |
windows-chrome |
Win32 + Chrome 151 | Sampled D3D11 WebGL + Segoe pack. On Linux: known WebGL −5% pixel-vs-name tell (not claimed fixed). | persona="windows-chrome" (alias windows-1080p) |
from hexium_browser import launch
launch() # Linux: linux-native + GeoIP tz/lang
launch(persona="linux-chrome") # Linux UA, sampled screen/CPU/RAM, host GPU/fonts
launch(persona="windows-chrome") # Win32 UA + D3D + Segoe pack
launch(fingerprint="off") # fingerprint patches off
launch(persona="windows-chrome", fingerprint="account-1") # same machine next timeWindows-on-Linux needs the Segoe pack shipped in the wrapper (HEXIUM_FONTS_DIR overrides). Desktop UA-CH model is empty (stock Chrome 151). Do not cartesian OS×GPU — one OS + one joint sample.
python examples/linux-chrome/linux_chrome_persona.py
python examples/windows-chrome/windows_chrome_persona.py --headedA fingerprint seed picks one coherent device. Same seed → same machine. Bare launch() mints a new seed (and a new hexium-session-*). profile="Work" or fingerprint="account-1" reuses persona.json.
| Surface | Source | Sticky with seed? |
|---|---|---|
| Screen (width/height/avail/DPR) | Joint Chrome desktop sample | Yes |
hardwareConcurrency, deviceMemory |
Same sample (Windows desktop min 4 GB) | Yes |
| WebGL vendor/renderer, WebGPU vendor/arch | Same sample (windows-chrome only) |
Yes |
UA-CH platformVersion (Win10 vs Win11) |
Same sample; clamped to 10.0.0 / 15.0.0 (never 19.0.0); UA rewritten to 151.0.7922.174 | Yes |
| Fonts | Fixed Windows pack on windows-chrome; host on linux-* |
Yes (not shuffled per page) |
Timezone, locale, navigator.languages, WebRTC IP |
GeoIP from egress IP (default on) | Follows the proxy/IP, not the seed |
| Canvas LSB noise | Off unless args=["--hexium-noise=on"] |
Seed-stable if you turn it on |
| Mouse / keys / scroll | humanize=True (default) |
No — new path each action (human_preset="default" or "careful") |
GeoIP does not re-roll the GPU. Explicit timezone= / locale= always win over GeoIP.
linux-chrome persona on example.com (examples/linux-chrome/linux_chrome_persona.py)
Linux x86_64 only for this alpha. Windows and macOS are not supported yet — we are actively working on them. Thanks for your patience.
Archives are named Hexium + engine version + OS + arch:
Hexium-{VERSION}-{os}-{arch}.tar.gz
This ship:
Hexium-151.0.7922.174.1-linux-x64.tar.gz
Unpack it and you get hexium-v151.0.7922.174.1/chrome (same layout as ~/.hexium/hexium-v{VERSION}/). Point HEXIUM_BINARY_PATH at that chrome, or run hexium-browser fetch.
The tarball is attached once, on the engine release — not on Python vX.Y.Z tags:
- Engine + binary: Hexium-151.0.7922.174.1
- Python package notes: Hexium_browser-0.1.1
hexium-browser fetch tries https://headlessx.dev/api/download first. On 404 it downloads:
https://github.com/HeadlessXLabs/hexium-browser/releases/download/Hexium-{VERSION}/Hexium-{VERSION}-linux-x64.tar.gz
If that version is gone, fetch takes the newest GitHub tag named Hexium-* (never the Python v0.1.0 tag).
Resolution order:
HEXIUM_BINARY_PATH(aliasHEXIUM_BINARY)$HEXIUM_OUT/hexium-v{VERSION}/chromeif it exists- Cache under
~/.hexium/hexium-v{VERSION}/ hexium-browser fetch— headlessx.dev API, then GitHub ReleasesHexium-{VERSION}
Pin the engine with HEXIUM_VERSION.
| Env | Job |
|---|---|
HEXIUM_DOWNLOAD_URL |
Primary download prefix (default https://headlessx.dev/api/download) |
HEXIUM_VERSION |
Engine version (151.0.7922.174.1) |
HEXIUM_BINARY_PATH |
Exact chrome path |
HEXIUM_OUT |
Local out root |
HEXIUM_CACHE_DIR |
Cache root (~/.hexium) |
HEXIUM_GITHUB_REPO |
Releases repo (default HeadlessXLabs/hexium-browser) |
hexium-browser fetch
hexium-browser info --quickGeoIP is on by default. With hexium-browser[geoip], Hexium maps the egress IP (proxy exit, or the machine public IP when there is no proxy) to timezone, locale, languages, and --hexium-webrtc-ip=. Pass geoip=False to opt out. Explicit timezone= / locale= always win. Without the extra, launch continues and timezone stays the sampled UTC. Missing GeoLite2 still returns the exit IP so WebRTC does not leak LAN.
| Doc | What |
|---|---|
| Changelog | 0.1.1 and later |
| Usage | launch(), personas, GeoIP, humanize, profiles |
| CI | Ubuntu Actions, Setup Hexium, pytest |
| Personas | linux-native, linux-chrome, windows-native, windows-chrome, macos-native |
| Contributing | Install, pytest, bug reports |
| Support | How to file issues |
| Security | Vulnerability reports |
| CLAUDE.md | Agent / contributor product rules |
Persona notes: linux-native · linux-chrome · windows-native · windows-chrome · macos-native
Some sites detect --headless=new even with the C++ persona. Run headed on a virtual display when you have no monitor:
# Install Xvfb (virtual framebuffer)
sudo apt install xvfb
# Start virtual display
Xvfb :99 -screen 0 1920x1080x24 &
export DISPLAY=:99from hexium_browser import launch
# Headed Chrome + residential proxy
browser = launch(headless=False, proxy="http://your-residential-proxy:port")
page = browser.new_page()
page.goto("https://example.com")
browser.close()That is a real headed window on Xvfb — no physical monitor. Combine with the config below. Pass show_cursor=False on stealth oracles (the highlighter is a DOM tell). Hexium does not solve CAPTCHAs.
Most blocks come from missing one of these three things, not from browser fingerprint detection:
from hexium_browser import launch
browser = launch(
proxy="http://your-residential-proxy:port", # residential IP — datacenter IPs get blocked by reputation alone
geoip=True, # default; timezone + locale follow the proxy exit IP
headless=False, # headed — some sites still detect --headless=new
humanize=True, # default; mouse, keys, scroll
)Need residential IPs? ProxyLane, this month's sponsor, gives Hexium users 35% off with code HEXIUM35.
geoip=True needs pip install 'hexium-browser[geoip]'. Without the extra, launch still works and timezone stays the sampled UTC.
If the proxy supports SOCKS5, prefer it — SOCKS5 tunnels raw TCP and avoids HTTP CONNECT issues some proxies have with HTTP/2:
browser = launch(
proxy="socks5://user:pass@proxy:1080",
geoip=True,
headless=False,
humanize=True,
)Linux default persona is linux-native. Windows default is windows-native. macOS default is macos-native. windows-chrome uses the wrapper Segoe pack (HEXIUM_FONTS_DIR to override). Named profile= keeps the same fingerprint; a bare launch() is a new visitor each time.
Some sites challenge first-time visitors with no cookies over HTTP/2. That is a Chromium / site behavior, not a Hexium-only tell. Warm cookies once on a named profile, then reuse it:
from hexium_browser import launch
# First run: warm up with --disable-http2
browser = launch(profile="Shop", args=["--disable-http2"])
page = browser.new_page()
page.goto("https://example.com") # writes cookies into ~/.hexium/profiles/Shop
browser.close()
# Later runs — same profile, no --disable-http2
browser = launch(profile="Shop")
page = browser.new_page()
page.goto("https://example.com") # returning visitor
browser.close()A bare launch() is a new hexium-session-* every time, so cookies never come back. Pin profile= (or user_data_dir=) when you need a returning visitor.
For a one-shot session, launch(args=["--disable-http2"]) forces HTTP/1.1. Only use that flag on sites that need it — most are fine on HTTP/2. SOCKS5 (proxy="socks5://user:pass@host:port") avoids HTTP CONNECT entirely.
Python hexium_browser already ships (PyPI). First-party packages still to land (thin bindings over the same launch() contract — not a new fingerprint stack):
- .NET / NuGet
- npm
- Go
- Rust crate
- Java/JVM bindings if demand appears
- Python API — GNU Affero GPL v3.0 only (not “or later”)
chromebinary — BINARY-LICENSE.md (Chromium notices + Hexium build)
Last reviewed: 14 Sep 2026





















