| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
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.
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
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().
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 (noise=false) — examples/linux-native/stealth_test.py | CreepJS — examples/linux-native/fingerprint_scan_test.py |
fingerprint-scan.com — 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:
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:
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
)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):
Last reviewed: 14 Sep 2026
| Back | FazBrowse Home | New Git URL |