Skip to content

Releases: KeyCode17/px-solver

v1.10.0

Choose a tag to compare

@github-actions github-actions released this 07 Aug 17:57
6cadc98

v1.10.0 — Per-crate documentation on crates.io

No code change from v1.9.0. This release exists to carry documentation to the registry: crates.io renders each crate's own README, versions are immutable, and the publish is gated on a minor bump — so the READMEs added after v1.9.0 needed a version of their own to become visible.

What's new

  • A README for all 19 workspace members. Each published crate's page now shows its purpose, an install snippet, its public surface, and the caveats specific to it — proxy credentials for pxsolver-harvester, the rotation math for pxsolver-camoufox, stub status for pxsolver-turnstile / pxsolver-captcha / pxsolver-datadome, and the v1.9.0 signature change for pxsolver-pipeline. Before this, all 16 pages showed a one-line description and nothing else.
  • The package/crate name split is documented. You depend on pxsolver-core but you use px_core::…. The pxsolver- prefix namespaces the family on crates.io; the source-level name stays short. This was not discoverable from a crates.io page.
  • .cargo/config.toml added, wiring up the cargo xtask alias. ADR-0016, the root README and xtask/README.md all documented cargo xtask <cmd>, but no alias file existed — every documented invocation failed with error: no such command: xtask.

Corrected

The root README told readers to run cargo install pxsolver-server pxsolver-cli. Both binaries are publish = false and have never been on crates.io, so that command has never worked. Only the 16 library crates are published; the binaries build from source:

git clone https://github.com/KeyCode17/px-solver
cd px-solver
cargo build --release -p px-server -p px-cli

Upgrading from v1.9.0

Nothing to do — the code is identical. If you are coming from v1.8.0 or earlier, note that v1.9.0 was source-breaking: ChallengeHandler::solve and SolveDispatcher::solve take a SolveAction / SolveRequest instead of &PageHtml / &str. See the v1.9.0 notes.

Verification

cargo fmt --check · clippy clean under the unwrap/expect/panic ban · full test suite green · 200-LOC gate green · all 16 crates published at 1.10.0 with readme_path populated.

PRs merged: #21 (per-crate READMEs), #22 (bump + xtask alias).

v1.9.0

Choose a tag to compare

@KeyCode17 KeyCode17 released this 07 Aug 17:30
f5b3ee8

v1.9.0 — Egress proxies actually reach the browser

Four proxy surfaces shipped through v1.8.0 and only one of them reached a browser. This release makes the per-request proxy real end to end, and corrects the docs that described behaviour the code never had.

The bug, precisely

Surface Before v1.9.0
PX_PROXIESProxyPoolSessionPool worked, /v1/fetch sessions only
POST /v1/solve body "proxy" deserialized into SolveRequestDto, never read
px-cli solve --proxy sent in the request body, dropped server-side
pxsolver-core::SolveRequest::with_proxy() published builder with zero consumers
HarvestRequest.proxy honoured by CamoufoxPool, ignored by ChromiumoxidePool

The cause was structural rather than a missed call site: SolveDispatcher::solve took &str and ChallengeHandler::solve took &PageHtml, so a proxy had no parameter to travel in, and both browser handlers built HarvestRequest::new(url) — which defaults proxy to None. ChromiumoxidePool::launch_browser had no --proxy-server argument at all.

What's new

  • Per-request egress on /v1/solve. ChallengeHandler::solve now takes a SolveAction { page, proxy } constructed at the HTTP edge; SolveDispatcher::solve takes pxsolver_core::SolveRequest, so the published builder is the type the edge maps into. Chromium, Camoufox and the native sensor path all honour it.
  • Native path proxying. SolveContext carries the proxy and SensorNativeSolver posts through a per-proxy reqwest client, cached in ProxyClients so the pooled TLS connection survives repeat solves.
  • The egress is part of the cache key. A bundle earned through proxy A is no longer replayed to a caller who asked for proxy B. A direct solve keeps fp_key = 0, so existing entries still resolve. (This is the fix for the reported "different proxy values return the same cached bundle" behaviour.)
  • Credentials are handled honestly. No browser engine can answer a proxy 407 — geckodriver's W3C proxy capability has no credential field and Chromium ignores userinfo without a CDP Fetch.authRequired handler. user:pass@ is now stripped with a warning naming the sanitized URL. reqwest does support proxy auth, so the native sensor path is exempt.
  • socks5h:// is normalized for Chromium, which has no such scheme and silently ignores specs it cannot parse.

Deliberately unchanged

/v1/solve never falls back to the PX_PROXIES rotation. A _px3 bundle is bound to the IP that earned it, so a caller who named no egress could not use a bundle harvested through a rotating one. Rotation stays where it pays: long-lived /v1/fetch Camoufox sessions, which consume the bundle themselves.

Docs corrected

  • docs/deployment.md claimed N × len(proxies) parallel egress paths. A session takes its proxy at spawn and holds it for the 300s TTL, so distinct egress IPs per domain is min(PX_FETCH_MAX_PER_DOMAIN, len(PX_PROXIES)) — ten proxies at the default N=2 gives a domain two IPs, not twenty.
  • The PX_PROXIES example showed user:pass@ for paths that cannot authenticate.
  • docs/runbook-native-bypass.md told operators PX_PROXIES covered the solve path, and its throughput soak built a direct reqwest client while claiming to run through their proxy. The soak now reads NATIVE_SOAK_PROXY (or the first PX_PROXIES entry) and prints the egress it used.
  • README gains a Proxies section. Rationale: ADR-0025.

⚠️ Source-breaking despite the minor bump

ChallengeHandler::solve and SolveDispatcher::solve changed signature. If you implement ChallengeHandler outside this workspace:

// before
async fn solve(&self, page: &PageHtml) -> Result<HandlerOutcome, AppError>

// after
async fn solve(&self, action: &SolveAction) -> Result<HandlerOutcome, AppError>
// action.page is the old argument; action.proxy is the requested egress

Everything else in the published surface is unchanged. A pxsolver-* = "1" pin will fail to compile on cargo update — this was shipped as a minor knowingly, and the trade is recorded in ADR-0025.

Release mechanics

xtask bump only ever rewrote [workspace.package] version, leaving the internal pxsolver-* dependency pins at 1.4.0 since that release. Minor bumps hid it (1.8.0 satisfies ^1.4.0). bump now re-pins them with the workspace version, and all pins track 1.9.0.

Also clears RUSTSEC-2026-0185 (quinn-proto) and RUSTSEC-2026-0190 (anyhow) — both transitive, lock-only.

Verification

cargo fmt --check · cargo clippy --workspace --all-targets --all-features under the unwrap/expect/panic ban, zero warnings · full workspace test suite, zero failures · 200-LOC gate green · cargo audit --deny warnings clean. New tests cover the proxy reaching the routing dispatcher, the Cloudflare harvester and the native solver, the --proxy-server argument shape and socks5h normalization, egress-scoped cache entries, and absent optionals staying off the wire.

PRs merged

  • #20feat(proxy): honour the per-request egress proxy end to end

Published to crates.io

All 16 pxsolver-* library crates are published at 1.9.0. The pxsolver-server and
pxsolver-cli binaries are publish = false — build them from source.

Commits since v1.8.0:

  • Merge pull request #20 from KeyCode17/feat/proxy-end-to-end (f5b3ee8)
  • chore(deps): clear RUSTSEC-2026-0185 and RUSTSEC-2026-0190 (2094431)
  • chore: bump to 1.9.0 (d6e263a)
  • fix(proxy): stop two silent drops on the wired-up egress path (24be22e)
  • style(errors): capitalize error messages in the proxy-touched harvester files (f534b63)
  • fix(xtask): re-pin internal deps when bumping the workspace version (ed4bf67)
  • feat(proxy): honour the per-request egress proxy end to end (9d3cdba)

v1.8.0

Choose a tag to compare

@github-actions github-actions released this 19 May 20:31
61c0d2c

v1.8.0 — Native PerimeterX bypass: dispatcher + capture/calibrate/soak

This release ships the wiring for the native px-3 sensor synthesis path from v1.7.0. The native solver is no longer just a library — it's runnable end-to-end via the dispatcher, and there's a complete operator workflow for capturing ground-truth payloads, calibrating field grammar, and measuring live throughput.

What's new

  • Dispatcher overlay (P1, #15). New PX_NATIVE_PROFILES=domain=path/to/profile.toml env var. When set, the solve dispatcher wraps the per-domain handler with a NativeFirstHandler (try native, fall back to Camoufox on error or non-solved). Opt-in; default behaviour unchanged.
  • XHR-hook captures (P2, #16). pxsolver-camoufox::capture_sensor spawns a session, injects a JSON.stringify + XMLHttpRequest.send monkey-patch, and dumps every plaintext [{t,d},…] sensor batch + every /b/s wire payload to JSON.
  • Calibrate command (P3, #16). px-cli calibrate <capture.json> diffs a real capture against default_batch, reporting missing tags / extra tags / per-tag missing keys. The closer the diff converges, the higher the trust score the native path will earn.
  • Throughput soak (P4, #17). cargo test -p pxsolver-native --test throughput_soak -- --ignored runs SensorNativeSolver::solve N× concurrently and asserts a configurable req/min target (default 40).
  • Operator runbook (P4, #17). docs/runbook-native-bypass.md walks the four-step procedure: capture → calibrate → enable → soak.

Status

  • Code complete. 132 workspace tests pass, clippy clean under the unwrap/expect/panic ban, all files ≤200 LOC.
  • Live validation pending. The bet from v1.7.0 ("can we actually hit ≥40 req/min through native synthesis?") still needs an operator with an AR residential proxy + Camoufox install to run the runbook. The infrastructure is now in place — the open question is whether default_batch earns a high enough trust score after calibration.

PRs merged

  • #15feat(server): wire SensorNativeSolver into solve dispatcher
  • #16feat: XHR-hook captures + calibrate command for native synthesis
  • #17feat(native): throughput soak + operator runbook for live bypass
  • #18chore: bump to 1.8.0

🤖 Generated with Claude Code

v1.7.0

Choose a tag to compare

@github-actions github-actions released this 19 May 19:07
1afd796

R-track decode + native sensor synthesis

This release completes the four open R-track phases for the pedidosya eT15wiaE PerimeterX tenant and lands the first five sprints of the native synthesis plan from ADR-0024 (supersedes ADR-0010).

What's new

  • R2 — gB(t) static decoder. Custom Babel transform inlines every gC(N) / gF(N) indirection in the captured init.js. 1 457 string literals decoded. Tools and outputs land under px-research/tools/decoder/ and px-research/deobf/eT15wiaE/.
  • R3 — sensor RE. The px-3 payload encryption is XOR(0x32) + base64 + vQ string-splice VM. No AES and no HMAC at the payload tier; integrity is the separate pxhc MD5 cookie. Notes in px-research/notes/eT15wiaE/r3-sensor-grammar-2026-05-20.md.
  • ADR-0024 activates native synthesis. ADR-0010 marked Superseded by ADR-0024.
  • N1 — cipher core. Dependency-free Rust ports of jw, h_p, v_m, v_l, v_n, v_q under px-native/src/cipher/. Each module is <100 LOC and round-trips byte-for-byte against a JS reference (px-research/tools/decoder/cipher_ref.js).
  • N2 — sensor encryptor. encrypt_sensor(events_json, pf, cu) chains the six primitives end-to-end matching the JS vP function. Fixed v_q to honour JS String.prototype.substring semantics (clamp + swap).
  • N3 — event collector. events/ module: SensorEvent, EventField, SyntheticIdentity, default_batch emitting PX561 / PX11978 / PX12457 baseline events.
  • N4 — SensorNativeSolver. Implements NativeSolver::solve: builds the payload, POSTs to ${origin}/${app_id_tag}${sensor_path}, parses Set-Cookie into PxCookieBundle.
  • N5 — TOML tenant profile. profile/schema.rs + bundled px-native/profiles/eT15wiaE.toml.
  • Stealth hardening (#10) — synthetic-user pool, humanize hooks, SOCKS5 auth-relay caps in px-camoufox.

Test coverage

  • 121 workspace tests pass, 0 failures
  • 30 in px-native (lib unit + cipher_fixtures + end_to_end)
  • cargo clippy --workspace --all-targets --all-features -- -D warnings -D clippy::unwrap_used -D clippy::expect_used -D clippy::panic -D clippy::dbg_macro -D clippy::todo -D clippy::unimplemented clean
  • All files ≤200 LOC (largest: sensor_solver.rs at 147)

Strategic constraints honoured

  • No third-party CAPTCHA services. Native synthesis is the only bypass path.
  • Throughput target ≥40 req/min remains gated on integrating SensorNativeSolver into the composition root and routing it ahead of the Camoufox harvester (planned for v1.8.0).

PRs merged

  • #10feat(camoufox): synthetic-user pool + humanize + SOCKS5 auth-relay caps
  • #11docs(r-track): R0 + R2-pass-1 captures for pedidosya eT15wiaE
  • #13feat(native): R2-R3 decode + N1-N5 native sensor synthesis (ADR-0024)
  • #14chore: bump to 1.7.0

🤖 Generated with Claude Code

v1.6.0

Choose a tag to compare

@KeyCode17 KeyCode17 released this 19 May 07:22

Release v1.6.0 (minor)

Changes since v1.5.0:

  • chore: bump to 1.6.0 (d3f0305)
  • Merge pull request #9 from KeyCode17/feat/proxy-rotation (66842e3)
  • feat(camoufox): rotate egress proxies across /v1/fetch sessions (78f8188)

Published to crates.io: yes (16 pxsolver-* crates)

v1.5.0

Choose a tag to compare

@KeyCode17 KeyCode17 released this 18 May 22:54

Release v1.5.0 (minor)

Changes since v1.4.0:

  • chore: bump to 1.5.0 (f9bba1b)
  • Merge pull request #8 from KeyCode17/feat/fetcher-throughput-tuning (c9c75ca)
  • perf(camoufox): tune fetcher for 30+ req/min target (0e7ab47)
  • feat(camoufox): per-domain persistent session for /v1/fetch (#7) (7acd612)

Published to crates.io: yes (16 pxsolver-* crates)

v1.4.0

Choose a tag to compare

@github-actions github-actions released this 18 May 18:53

Release v1.4.0 (minor)

Changes since v1.3.0:

  • chore: bump to 1.4.0 (79090b4)
  • Merge pull request #6 from KeyCode17/feat/v1-fetch-endpoint (7e142f7)
  • Merge pull request #5 from KeyCode17/fix/cache-preserve-harvester-ua (7b4f879)
  • feat(server): /v1/fetch endpoint backed by handler-owned browsers (17b9c9e)
  • fix(server): cache hits preserve harvester user-agent (0b93d4a)

Published to crates.io: yes (16 pxsolver-* crates)

v1.3.0

Choose a tag to compare

@github-actions github-actions released this 18 May 07:45

Release v1.3.0 (minor)

Changes since v1.2.0:

  • chore: bump to 1.3.0 (46354ee)
  • ci(release): port rust-ai-surfer publish pattern (search+retry) (19028a3)
  • Merge pull request #2 from KeyCode17/docs/readme-refresh (2764ce7)
  • Merge pull request #3 from KeyCode17/feat/cli-phase04 (d42cacc)
  • feat(server): drain in-flight solves on SIGTERM / Ctrl-C (#4) (c24338b)
  • refactor(auth): make allowlist list() order deterministic (#1) (7ccc300)
  • feat(cli): complete Phase 04 subcommand surface (5719089)
  • docs(readme): reflect v1.2.0 published state (1de8e24)
  • ci(release): send User-Agent to crates.io API (skip-if-exists) (a6cd163)
  • ci(release): pre-flight skips already-published layer-0 crates (bae493e)
  • ci(release): rate-limit aware publish loop (1ca83d5)

Published to crates.io: yes (16 pxsolver-* crates)

v1.2.0

Choose a tag to compare

@KeyCode17 KeyCode17 released this 17 May 15:32

Release v1.2.0 (minor)

Changes since v1.1.0:

  • chore: bump to 1.2.0 (b90ffe5)
  • ci(release): tagged release, crates.io gate on main + minor/major (7cc92e4)
  • build(workspace): rename library crates to pxsolver-* for crates.io (2adea4a)

Published to crates.io: yes (16 pxsolver-* crates)

v1.1.0

Choose a tag to compare

@KeyCode17 KeyCode17 released this 17 May 10:28

Camoufox CF bypass + per-handler routing + latency histogram

Minor release.