Skip to content

Feat/captchakraken and js support - #7

Open
JWriter20 wants to merge 11 commits into
mainfrom
feat/captchakrakenAndJSSupport
Open

Feat/captchakraken and js support#7
JWriter20 wants to merge 11 commits into
mainfrom
feat/captchakrakenAndJSSupport

Conversation

@JWriter20

Copy link
Copy Markdown
Owner

Related Issue

Closes #

Description

Type of Change

  • Bug fix
  • New feature
  • Documentation update
  • Other

Testing

Fingerprint Report

Please submit a report from both the service tester and build tester.

Fingerprint report

Checklist

  • I have linked a related issue above
  • My changes are focused on a single logical change
  • I have added testing instructions which include the desired result
  • Service tests pass (for python library changes) -./service-tester/run_tests.sh --browser-version official/prerelease/146.0.1-alpha.25 (attach screenshot) temporarily out of service lol
  • Build test passes (for patch changes) - ./build-tester/run_tests.sh A score of at least 1000 must be achieved (attach screenshot)

JWriter20 and others added 11 commits August 3, 2026 15:12
Firefox's EfficientCanvasRandomization RFP target (a desktop default in
152) is honoured by the 2D canvas paths but not by WebGL readback --
ClientWebGLContext.cpp only randomizes for ImageExtraction::Randomize, so
a GPU-backed context returns un-noised pixels from readPixels() and from
the framebuffer snapshot path. Extend both checks to EfficientRandomize,
matching the patch already carried in Holo.

2D canvas is left alone: it renders through Skia in software and is
already cross-OS identical on the same arch.

Also pin the release matrix to linux/x86_64.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Firefox mints the fingerprinting randomization key as a UUID once per
browser session, so canvas and WebGL noise drift on every launch even
when the profile is reused. Derive it from `canvas:seed` in
GetBrowsingSessionKey() instead -- that is the single root feeding
CookieJarSettings::mFingerprintingRandomKey, and from there the SipHash
keys in GenerateCanvasKeyFromImageData() that every RandomizeElements()
caller uses. Both WebGL readback sites enabled by
webgl-efficient-randomize.patch go through that call, so the seed now
drives WebGL noise as well.

The origin attribute suffix is mixed in, so separate userContextIds
still diverge. `canvas:seed = 0` disables the noise entirely, matching
audio:seed and fonts:spacing_seed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The job-level `working-directory: browser` default also applies to the
"Remove unwanted tools" step, which runs before actions/checkout. The
directory does not exist yet at that point, so the step died with "An
error occurred trying to start process '/usr/bin/bash' with working
directory .../browser".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
macOS is cross-compiled from the Linux runner: multibuild.py runs
`make setup-macos-sdk` (fetches the SDK from Apple's CDN, no secrets)
and generate-assets-car.sh falls back to the committed Assets.car when
the host is not Darwin.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bridges Camoufox to CaptchaKraken. Camoufox gets you a browser a site cannot
fingerprint as automated; it does not, on its own, get you past a challenge
already on screen.

    with Camoufox(captcha="ck_live_…") as browser:
        page = browser.new_page()
        solve_captcha(page)

A token alone resolves to the hosted endpoint. CaptchaKraken's own base-URL
lookup falls back to localhost when it finds no endpoint and no credentials
file — right for a self-hoster, useless for someone handed a cloud token and
nothing else, whose every solve would dial a dead local port. `captcha=` also
takes a URL for your own vLLM, or a dict for both, or a `model` to name which
served adapter to ask for.

The optional dependency is resolved inside the call, so importing camoufox never
requires the extra.

WATCHER
`watch_captcha` / `watchCaptcha` install an auto-solver, for scripts that do not
know where a challenge will interrupt them. It injects nothing into the page —
no observer, no exposed binding — and drives CaptchaKraken's detection from the
driver side on a timer. Under Camoufox those DOM reads land in the sandboxed
Juggler world for free: that is the default for all Playwright evaluation here,
with main_world_eval / an "mw:" prefix as the opt-OUT, and nothing opts out.

ATTRIBUTION IS PER-SOLVE, NOT PER-INSTALL
Every request carries `camoufox/<version>` as X-CK-Client, which is how
camoufox-originated solves are credited. `applied()` restores the environment
when its block unwinds, so building a watcher inside one would hold the tag and
the launch-time key only until the handle was returned — long before any captcha
appeared, leaving every later solve unattributed and a hosted user's request
unauthenticated. `tagged()` / `_TaggedSolver` scope them to each solve instead,
and that seam is what the tests drive.

The tag carries no pricing power: the server derives the billable puzzle class
from the request body, so a forged tag cannot buy cheaper inference.

Python is synchronous, mirroring CaptchaKraken's driver — a sync Playwright
handle cannot be driven from an event loop, so AsyncCamoufox cannot call it and
the watcher blocks rather than returning a handle. The TypeScript port is async
and has neither limit.
main moved on by 13 commits (upstream sponsor churn, the platformdirs
fontconfig fix, python 0.5.5, beta.29). Only two files actually
conflicted; the rest merged through git's rename detection, which
correctly followed upstream.sh -> browser/upstream.sh and
pythonlib/camoufox/utils.py -> python/src/utils.py, so the monorepo
layout survives intact.

.gitignore: kept ours. main's only change was the `.DS_store` ->
`.DS_Store` case fix (daijro#711), which the rewritten monorepo ignore already
spells correctly. Taking main's side would have reinstated the bare
`/browser/` ignore that erases our source folder.

README.md: kept ours. main's edits were entirely inside the sponsor
tables and a NodeMaven link, all of it in content the monorepo README
does not carry.

utils.py merged to exactly what main already has: INSTALL_DIR is
Path(user_cache_dir("camoufox")), so upstream's XDG_CACHE_HOME fix
(daijro#654) is carried semantically, not just textually.

Python is now 0.5.5 while the npm twin is still 0.5.4, so the
attribution tag differs per launcher — Python derives it from package
metadata, TypeScript from LIBRARY_VERSION.
`biome check` is a CI gate and this ordering has been failing it since
the captcha exports landed. Auto-fix only; no runtime change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A build previously shipped on the strength of having compiled. This runs both
halves of the question against the packaged artifact -- does it still drive,
and what does it leak -- on the OS it was built for, and attaches the answer
to the release.

  build (ubuntu, cross-compiles)  ->  verify (ubuntu | macos | windows)  ->  release

`build` gains windows/x86_64, so the matrix is now one build per
GitHub-hosted runner it has to run on: ubuntu-24.04 is x86_64, macos-latest
is arm64, windows-latest is x86_64. Cross-compiling and never executing was
the reason windows could be skipped before; it cannot be now.

RELEASE-TESTER

New root suite, alongside build-tester/ and service-tester/, taking an
extracted package rather than a raw binary or a wheel:

  playwright  browser/tests, the upstream suite minus the cases our patches
              break on purpose. Those skips are counted, not hidden, and the
              pass rate is scored against what actually ran -- scoring against
              the total would make the suite look worse the more we harden the
              browser.
  sundial     the /automated scan, once per emulated OS. Reading a category
              across those columns is what separates a leak that is always
              there from one that only appears when the build claims to be
              something it is not.

SUNDIAL IS KEYED, NOT LOGGED INTO

/automated is matched before the cookie session check in
functions/_middleware.js, so a username and password never reach it -- the
route authenticates on a `key` query parameter alone and 401s without one. The
secret is AUTOMATION_PRIVATE_KEY; the guest key is served a stubbed
vectors-private.js and so reports a cleaner build than you have. Unset, the
scan is skipped and the Playwright half still reports: a missing key is a
pipeline gap, not a regression, and does not flip the verdict.

mode=raw is load-bearing. Without it the page boots a virtualized viewer and
#report-raw never holds the whole document. The driver waits on the page's own
terminal state (data-sundial-state) rather than a timer.

geoip is on by default: sundial cross-checks claimed timezone and locale
against the geo-IP of the connecting address, so without it a scan fails
Locale and Network on the runner's configuration rather than on the build.
The same reports carry the runner's public IP, city and ASN, and these get
attached to a public release -- so those fields are redacted on the way out,
leaving the category tallies untouched.

WHAT PUBLISHING CORRECTLY REQUIRED

- Release assets are a flat namespace. Uploading artifacts/**/* would have
  three results.json collide and two silently vanish, so combine.py stages
  them per build first.
- A hung or crashed suite returns a recorded error instead of raising: the
  point of the run is to publish numbers, and blowing up would lose the
  stealth scan alongside the Playwright one.
- verify is if: always(), matching build's fail-fast: false -- a windows build
  that fails to compile must not cost us the linux and macos results.

Not `pip install -e ./python`: poetry-core cannot express this project's
src/ -> camoufox remap in an editable install, and quietly puts python/src on
sys.path instead, which makes `import utils` work and `import camoufox` fail.

browser/tests grows ci-requirements.txt because local-requirements.txt pulls
auditwheel, which is manylinux tooling with no business on a macOS or Windows
runner; a test keeps the pins in the two files from drifting. Its bare
`playwright` is also now pinned <1.61 to match python/pyproject.toml -- 1.61+
sends Juggler params this browser rejects, which would have errored every case
at launch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
release-tester needs a binary, and build.yml only ever hands it one it just
spent an hour per target compiling. That is the wrong shape for checking that
the sundial key works, or for re-scanning a leak after sundial itself changes:
neither needs a new build.

This pulls the package off an existing release instead and runs the same
suites against it, defaulting to the sundial half alone -- the Playwright
dependencies and its Firefox download are skipped entirely unless the suite
input asks for them.

Version, release and arch come from the asset filename rather than
browser/upstream.sh, because the build under test shipped from some other
commit than the branch this runs on.

A choice input cannot carry its own runner, so target -> runner is mapped
through fromJSON, using the same pairing build.yml relies on: linux is x86_64
on ubuntu-24.04, macos is arm64 on macos-latest, windows is x86_64 on
windows-latest.

GitHub only registers workflow_dispatch for workflows on the default branch,
so this becomes dispatchable once it reaches main.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant