Adds DumpFormat::Cookies that serializes the full browser cookie jar as
pretty-printed JSON after page load. Unlike document.cookie, this captures
HttpOnly cookies inaccessible from JS — useful for extracting session
tokens set by anti-bot challenges.
SameSite is case-insensitive per RFC 6265bis but servers send it in any
casing (lax, LAX, Lax). Normalize to title-case at parse time so
downstream comparisons work regardless. Unknown values fall back to Lax
per spec.
* feat(stealth): randomize browser profile per session
Add a pool of 8 realistic browser profiles (Win10/Win11/macOS, Chrome
143-146). One is picked randomly per BrowserContext instead of always
advertising Linux, which is a strong bot signal.
Each profile keeps navigator.platform, userAgentData.platform, and
userAgentData.platformVersion internally consistent with the UA string.
- profiles.rs: static PROFILES pool + random_profile() picker
- context.rs: pick profile on construction, store platform fields
- page.rs: call rt.set_platform() alongside rt.set_user_agent()
- runtime.rs: add set_platform() to inject __obscura_platform globals
- bootstrap.js: read platform/userAgentData from globals (default Win32)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(stealth): match GPU renderer pool to OS profile
Windows profiles get ANGLE Direct3D11 renderers; macOS profiles get
ANGLE Metal renderers. Vendor strings updated to match (Google Inc. (Apple)
for Apple Silicon/Intel Mac, Google Inc. (NVIDIA/AMD/Intel) for Windows).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(stealth): navigator.webdriver returns false instead of undefined
Browsers set navigator.webdriver to false when automation is not active.
Returning undefined is a detectable bot signal — fingerprinting scripts
check typeof navigator.webdriver === 'boolean' as part of headless detection.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Make stealth profile stable by default, rotation opt-in
Picking a random profile per BrowserContext meant one address would cycle
through different browser identities, which is itself a bot signal, and the
rotated profile carries no matching TLS or timezone fingerprint. Default to a
single stable profile; opt into rotation with OBSCURA_ROTATE_PROFILE, or pin
one with OBSCURA_PROFILE=<index>.
Drive timezone from the process TZ so Intl.DateTimeFormat and Date report one
consistent zone. Default Europe/Berlin; override with OBSCURA_TIMEZONE.
Update navigator.webdriver test to expect false, matching real Chrome.
---------
Co-authored-by: SGavrl <277843151+SGavrl@users.noreply.github.com>