Skip to content

Fingerprint and Environment

losenine edited this page Oct 31, 2026 · 1 revision

English · 中文

Fingerprint and Environment

ruyiJS does not inject page globals to fake a browser. You give each Context a validated environment profile, and the page's own scripts read Navigator, Screen, the viewport, WebGL and the rest from the native browser APIs.

Setting a profile

from ruyijs import Context

with Context() as context:
    context.configure_fingerprint({
        "navigator": {"language": "zh-CN", "hardwareConcurrency": 8},
        "screen": {"width": 1920, "height": 1080},
        "viewport": {"width": 1280, "height": 720},
        "locale": {"timezone": "Asia/Shanghai"},
    })
    print(context.eval("[navigator.language, screen.width]"))  # ['zh-CN', 1920]

Or pass the profile to the constructor, as the DataDome example does:

ENVIRONMENT = {"screen": {"width": 1920, "height": 1080, "availWidth": 1920, "availHeight": 1040},
               "viewport": {"width": 1904, "height": 987}, "locale": {"timezone": "Asia/Shanghai"}}

with Context(timeout=90, environment=ENVIRONMENT) as context:
    ...

How it behaves (from the docstrings):

  • configure_fingerprint() and configure_environment() accept the same groups and schema; the first simply calls the second.
  • A profile is partial. The fields you pass are merged into the current state atomically.
  • Fields and ranges are validated by the runtime. A rejected configuration raises (as a JSError carrying the runtime's message) and leaves the existing state unchanged, so you can fix it and retry.
  • Configure before the first page operation, so every script observes one profile.
  • The call returns the resulting environment snapshot. get_defaults() returns the fixed Firefox baseline, independent of your overrides.
  • The argument must be plain JSON data: a dict with string keys and None / str / bool / int / finite float / list / dict values.

Groups used in the README and the shipped examples

The schema belongs to the runtime. The table lists the groups and keys that the README and the shipped examples actually set — it is not the full schema. Call get_defaults() to inspect the baseline.

Group Keys seen in the README and examples
navigator userAgent, platform, appCodeName, appName, appVersion, product, productSub, vendor, vendorSub, language, languages, hardwareConcurrency, maxTouchPoints, cookieEnabled, webdriver, oscpu, buildID, doNotTrack, pdfViewerEnabled, onLine, globalPrivacyControl
screen width, height, availWidth, availHeight, availLeft, availTop, colorDepth, pixelDepth
viewport width, height, devicePixelRatio
locale timezone (an IANA name such as "Asia/Shanghai")
webgl vendor, renderer, version, glslVersion, unmaskedVendor, unmaskedRenderer, version2, glslVersion2, attributes, extensions, limits
speech voices, langs, default

The full Firefox-on-Windows profile the Akamai example applies is PROFILE in examples/akamai/akamai.py. A subset of it:

PROFILE_SUBSET = {
    "navigator": {
        "platform": "Win32",
        "language": "zh-CN",
        "languages": ["zh-CN", "zh", "en-US", "en"],
        "hardwareConcurrency": 12,
        "maxTouchPoints": 0,
        "webdriver": False,
        "oscpu": "Windows NT 10.0; Win64; x64",
    },
    "screen": {"width": 1920, "height": 1080, "availWidth": 1920, "availHeight": 1040,
               "availLeft": 0, "availTop": 0, "colorDepth": 24, "pixelDepth": 24},
    "viewport": {"width": 1536, "height": 864, "devicePixelRatio": 1.25},
    "locale": {"timezone": "Asia/Shanghai"},
}

Keep the TLS version and the user agent together

Context(tls=...) changes only the network fingerprint (TLS ClientHello, HTTP/2 settings, HTTP/3 priorities). When the page should agree, set a user agent of the same Firefox version. The Akamai example derives it from its --tls option; the same idea in a few lines:

version = "120"
with Context(tls=version) as context:
    context.configure_fingerprint({"navigator": {"userAgent":
        f"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:{version}.0) Gecko/20100101 Firefox/{version}.0"}})

More on TLS profiles: Network and TLS.

Clock

context.configure_clock("logical", epoch_ms=1700000000000)   # deterministic
context.configure_clock("system")                            # live work
  • Logical: requires an explicit integer epoch in milliseconds. Time moves only through advance(), tick() or sleep(), without wall-clock waiting, so runs are reproducible.
  • System: advance() waits in real time. The live CreepJS and Akamai examples select it before navigating.
  • Configure the clock before page work or timer advancement. clock_status() reads the current clock and the pending timer count without running timers.

Timer precision is Firefox 159's (CHANGELOG 0.2.1): performance.now(), timeOrigin, Date.now(), Event.timeStamp and every navigation/resource time are clamped to 1 ms with Firefox's jittered rounding, one seed per document.

Trusted input

Scripts can tell synthetic events from user input through isTrusted; the Akamai example README lists this among the checks. The input methods create trusted events natively, in Firefox's order:

context.click("document.querySelector('#submit')")
context.type("hello", target="document.querySelector('input[name=q]')")
context.press("Enter")
context.dispatch_input("mousemove", target="document.documentElement", clientX=200, clientY=150)

target is always a JavaScript expression. dispatch_input sends exactly one Mouse/Pointer event with no layout or hit testing; click, type and press produce the full user-like sequence. See API Reference for timing parameters.

Reproducible runs

With the logical clock, the replay controls let you re-run a captured flow deterministically:

  • configure_random(state0, state1) — the initial page's Math.random state;
  • configure_crypto_replay(documents) — crypto.getRandomValues and randomUUID values per document;
  • configure_navigation_replay(documents) — navigation timing fields per document;
  • configure_timer_replay(), pending_timers(), run_timer(...) — explicit timer selection (message replay has matching methods);
  • put_resource(...) — every response served offline (Network and TLS).

Signatures and limits: API Reference.

Seeing what a page reads

Create the context with mode="debug" to record accesses to registered browser API members, then read them with peek_api_log(), get_api_log() (drains) or clear_api_log(). Properties a script defines itself are not recorded.

Clone this wiki locally