Skip to main content

shardx (Python)

Self-contained Python SDK for the ShardX anti-detect browser by the ProxyShard team.

This package does not depend on the desktop launcher. On first use it downloads the patched Chromium 152 engine, Widevine CDM, and the 220-profile fingerprint library from our CDN into a local cache, then launches isolated browser sessions on demand.

Driven by patchright (stealth-patched Playwright) — sdk.session() returns a ready-to-use Browser instance, no manual connect_over_cdp plumbing.

Install

pip install shardx

Supported hosts: macOS arm64, Windows x64, Linux x64.

Linux system dependencies

The bundled Chromium engine needs unzip + the standard set of shared libraries any Chromium fork links against. On a fresh Debian / Ubuntu:

sudo apt install -y \
  unzip ca-certificates fonts-liberation \
  libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 \
  libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 \
  libgbm1 libpango-1.0-0 libcairo2 libasound2 libxshmfence1

When launching as root or inside Docker, pass --no-sandbox and --disable-dev-shm-usage via extra_args=:

await sdk.session(..., extra_args=["--no-sandbox", "--disable-dev-shm-usage"])

Quick start

import asyncio
from shardx import ShardX

async def main():
    sdk = ShardX()
    # Engine + Widevine + fingerprint library auto-download from CDN on
    # the first `session`/`launch`/`list_profiles` call (~170 MB once,
    # etag-cached afterward).  No separate install step.

    # Create a persistent profile from a library template (or create_profile()
    # for a random one). Library templates aren't launched directly — this
    # freezes an enriched copy under a unique id you can return to. Do it once.
    profile = sdk.create_profile("win-rtx4060")

    # Launch + drive in one call. Yields a patchright `Browser`.
    async with sdk.session(profile, proxy="socks5://user:pass@host:port") as browser:
        ctx = browser.contexts[0]
        page = await ctx.new_page()
        await page.goto("https://browserleaks.com/quic")
        print(await page.title())

        # Inspect what the SDK resolved before launch:
        sess = browser._shardx
        print(sess.geo)                   # GeoInfo(...) from ip-api / ipapi.co
        print(sess.proxy_udp_ms,          # UDP RTT in ms or None
              sess.quic_enabled,          # bool
              sess.webrtc_mode)           # "auto" | "tcp_only" | "block"
    # browser + udd shut down cleanly on exit

asyncio.run(main())

Random profile

# create_profile() with no id freezes a random library template (filter the
# pool with platform="Windows"). It already randomises hardware_concurrency /
# device_memory / platform_version once, at creation.
profile = sdk.create_profile(platform="Windows")
async with sdk.session(profile) as browser:
    page = await browser.contexts[0].new_page()
    ...

Discover bundled profiles

print(sdk.list_profiles()[:5])
# ['linux-gt1030', 'linux-gtx1050', 'mac-m1-air13', 'mac-m1-imac24', 'mac-m1-max-mbp14']

print(sdk.list_profiles(platform="Windows")[:5])

profile = sdk.random_profile(platform="macOS")
print(profile.id, profile.config["webgl"]["renderer"])

Validate a proxy before binding

print(sdk.check_proxy("socks5://user:pass@host:port"))
# {
#   'udp_ms': 142.3,
#   'geo': GeoInfo(country_code='DE', timezone='Europe/Berlin', ...),
#   'would_enable_quic': True,
#   'would_set_webrtc': 'auto',
# }

Persistent profiles

list_profiles() / random_profile() hand back library templates — launch one and it re-reads the same template every time. For a profile you can return to (same fingerprint and cookies/cache) or delete, create a saved profile: it freezes a template (or a random one) with randomized hardware/platform_version under a fresh unique id in its own folder <cache>/profiles/<id>/, exactly like the desktop launcher's "create profile".

sdk = ShardX()

# Create once (random template, or pass a library id like "win-rtx4060").
profile = sdk.create_profile()
print(profile.id)

print(sdk.list_saved_profiles())          # ['<id>', ...]

# Launch it. randomize stays False → the frozen fingerprint is stable;
# cookies/cache persist in the profile's folder across runs.
async with sdk.session(profile) as browser:
    ...

# Later — even a different process — reopen by id: same fingerprint + state.
profile = sdk.open_profile("<id>")

# Remove the profile and all its state when you're done.
sdk.delete_profile("<id>")

Saved profiles never touch the bundled S3 library: templates stay read-only in <cache>/fingerprints/*.json, saved profiles live in <cache>/profiles/<id>/ (holding profile.json + the browser's user-data-dir).

Human input and finger gestures

Motion is the engine's own input domain: pointer trajectories that obey Fitts's law, key-by-key typing with log-normal gaps, and the gestures a cursor cannot make. It runs inside the browser process — nothing is injected into the page — and it does not appear in /json/protocol, so a page cannot discover it.

from shardx import ShardX, Motion

async with sdk.session(profile) as browser:
    motion = await Motion.attach(browser)

    # desktop profile
    await motion.create_pointer(20, 20)
    await motion.glide_to(640, 360, target_width=220)   # the field's real width
    await motion.tap()
    await motion.enter_text("hello there")
    await motion.destroy_pointer()

    # phone profile
    await motion.touch_swipe(200, 600, 200, 200, flick=True)   # scroll and fling
    await motion.touch_long_press(200, 300)                    # context menu
    await motion.pinch(200, 400, 2)                            # zoom in
    o = await motion.set_orientation(90)
    # o → {'angle': 90, 'type': 'landscape-primary', 'screenWidth': 844, ...}

A profile that claims a touchscreen has no cursor, and the core enforces it both ways: the pointer commands are refused on such a profile, and the finger commands are refused on every other. There is no touchScroll and no fling command — gestures are built in the browser out of the touch stream, so a real swipe produces the scroll and the fling with the velocity the browser's own tracker fitted. touch_drag differs from touch_swipe in the wait before the travel: that wait is when a page's drag-and-drop starts, and a stroke that begins earlier is a scroll.

set_orientation is physical and takes time — the sensors move first and the picture commits at the end, the order a handset produces. It returns once the new angle has reached the page, so screen.width read straight after is already the turned one.

Anti-fingerprint noise

Per-vector noise (canvas / WebGL / audio / DOMRect / sensors / fonts) is off by default. set_noise(...) is declarative — exactly the vectors you list are enabled (with soft defaults), every other one is turned off:

profile.set_noise("canvas", "audio", "webgl")   # only these three on
profile.set_noise("canvas")                       # audio + webgl now off again
profile.set_noise()                               # all off
sdk.save_profile(profile)                          # persist the choice

Seeds are derived per-profile at launch — stable across runs, unique per profile — so two profiles with the same vectors enabled still produce different canvas/audio/WebGL fingerprints. Soft defaults: WebGL intensity 0.0005, DOMRect max_offset 1.

Pre-launch checks

Every call to sdk.session() / sdk.launch() runs the same pre-spawn pipeline the desktop launcher uses:

  1. resolve_auto_fields — if the profile has "auto" sentinels for timezone, navigator.language, or geolocation.mode, the SDK makes a live geo lookup through the bound proxy (ip-api.com by default). It then writes concrete values: timezone (from the API, never a static table), accept_language chain, languages, icu_locale (always overwritten so Intl.* matches navigator.language), and lat/lng. Proxy-via failure → direct geo → host $LANG / $TZ as last-resort fallback. The chosen geo is surfaced on session.geo.
  2. apply_screen_strategy — see below.
  3. probe_udp — SOCKS5 UDP_ASSOCIATE round-trip. If it fails, QUIC is force-disabled and WebRTC switches to tcp_only automatically.

Screen strategy

screen_mode kw to session() / launch():

  • "profile" — keep whatever the fingerprint claims.
  • "cap_to_host" — macOS default. If the host monitor is smaller than the FP screen, scale screen.* + window.* down proportionally; otherwise no-op.
  • "use_host" — Windows/Linux default. Overwrite screen.* with the real monitor (minus a 40 px Windows taskbar) and recompute window.outer_* / window.inner_* accordingly.

Default mode is picked from navigator.platform. Override per launch:

async with sdk.session(profile, screen_mode="profile") as browser:
    ...

Host-aware hardware randomisation

randomize=True re-picks hardware_concurrency, device_memory, and platform_version before the launch — using the same logic as the desktop launcher (randomize_hardware in lib.rs):

  • macOS profiles use the curated MAC_HW_CONFIGS table by id.
  • Windows / Linux profiles bracket the host's logical CPU count within [host − 4, host + 2] from the real x86 set [4, 6, 8, 12, 16, 20, 24, 28, 32]; device_memory is floored by core count (≥ 12 → 16, else 8) and capped by host_ram_bucket_gb() (8 / 16 / 32 GiB bucketed from sysctl hw.memsize / /proc/meminfo / Get-CimInstance Win32_ComputerSystem).

So a profile launched on an 8-core / 16 GB laptop will never claim 32 cores / 128 GB of RAM — keeps fingerprints internally consistent with real-world hardware.

Override fingerprint fields

profile = sdk.library.load("win-rtx4060").with_override(
    name="my-account",
    timezone="Europe/Berlin",
    navigator={"language": "de-DE"},
)
async with sdk.session(profile, proxy="socks5://...") as browser:
    ...

Use your own fingerprint JSON

from shardx import Profile

profile = Profile.from_file("/path/to/my-custom.json")
async with sdk.session(profile) as browser:
    ...

WebRTC policy

async with sdk.session(
    profile,
    proxy="socks5://...",
    webrtc="tcp_only",                # or "block" | "auto" (default)
    webrtc_public_ip="203.0.113.42",  # advertised in ICE candidates
) as browser:
    ...

Advanced: raw launch without patchright

If you'd rather drive the browser with a different CDP client (raw pychrome, pyppeteer, your own WebSocket), skip session() and use launch() directly:

profile = sdk.create_profile("win-rtx4060")
sess = sdk.launch(profile, proxy="socks5://...", cdp=True)
print(sess.cdp_url)        # ws://127.0.0.1:54113/devtools/browser/c0a3…
# … drive it yourself …
sess.stop()

launch() does the same pre-launch pipeline (auto-resolve, screen strategy, UDP probe, hw randomisation) and returns a BrowserSession with cdp_url, geo, proxy_udp_ms, quic_enabled, webrtc_mode, user_data_dir, and stop().

Cache layout

~/Library/Application Support/shardx-sdk/    (mac)
%LOCALAPPDATA%\shardx-sdk\                   (win)
~/.cache/shardx-sdk/                         (linux)
├── manifest.json             ← etag cache for browser/widevine/fingerprints
├── ShardX-Mac-arm64/         ← extracted engine
│   └── ShardX.app/…
├── fingerprints/             ← 220 bundled .json profiles
│   ├── win-rtx4060.json
│   └── …
└── profiles/                 ← saved profiles (create_profile) + their state
    └── <profile-id>/
        ├── profile.json      ← the frozen fingerprint config
        └── …                 ← user-data-dir: cookies, IndexedDB, cache

Override the cache root:

sdk = ShardX(cache_dir="/data/shardx")

Update the runtime

The SDK auto-checks remote etags on the first session/launch/list_profiles call of each process and re-downloads anything that changed. To force a re-download mid-process (e.g. CI scenarios):

sdk.runtime.install(force=True)

License

MIT (this SDK). The Chromium-fork engine binary downloaded at runtime is a closed-source product — see the main repo for engine licensing.

Metadata

Release files for shardx 2.0.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for shardx 2.0.3
File Size Uploaded
shardx-2.0.3.tar.gz 34.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for shardx 2.0.3
File Interpreter ABI Platform
shardx-2.0.3-py3-none-any.whl Python 3 none any Details

Total release size: 74.3 kB

Release files / shardx-2.0.3.tar.gz

Download URL shardx-2.0.3.tar.gz
Size 34.7 kB
Tags Source
SHA-256 checksum
How to use checksums
5547c421b3841a31cc7536cbf5d37b2e761ce7b09370fab46007c598e9d8fdb9
BLAKE2b-256 checksum
How to use checksums
05fb70995a65e9a1455baf6bbb5d175d0f5b0d816b8082bb1a6bf949909bf415
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Release files / shardx-2.0.3-py3-none-any.whl

Download URL shardx-2.0.3-py3-none-any.whl
Size 39.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d2e72a9144258ddfe3cc4378c0cbcfbeaa4527a4269adc70a96e6ae75c9f7e57
BLAKE2b-256 checksum
How to use checksums
9948030627ec174777c23e9b8d22ab5bdf26da4b6fe1a9644a8e012845fd460a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Release history Release notifications | RSS feed

This release

2.0.3 This release

2 release files

2.0.1

2 release files

2.0.0

2 release files

0.1.11

2 release files

0.1.10

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page