From 1ee798b326a91cb4a9a94cbad728222a1d07404d Mon Sep 17 00:00:00 2001 From: KeyCode17 Date: Sat, 8 Aug 2026 00:48:26 +0700 Subject: [PATCH] docs: give every workspace member its own README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit crates.io renders each crate's own README, and all 16 published crates had none — their pages showed a bare description with no usage, no link back to the workspace, and no way to tell a shipping handler from a detection-only stub. Each now carries its purpose, install snippet, public surface and the caveats specific to it, with `readme` declared explicitly rather than left to filename auto-detection. Fixes a false claim in the root README while in there: it told readers to `cargo install pxsolver-server pxsolver-cli`, but both binaries are `publish = false` and have never been on crates.io. Only the 16 library crates are published; the binaries build from source. The root README now lists what is actually installable from where. Also documents the package/crate name split — you depend on `pxsolver-core` but `use px_core::…`, which is not guessable from the crates.io page alone. px-server, px-cli and xtask are unpublished but get READMEs too; they are what a reader browsing the repo lands on. --- README.md | 29 ++++++++++++++--- px-auth/Cargo.toml | 1 + px-auth/README.md | 53 +++++++++++++++++++++++++++++++ px-cache/Cargo.toml | 1 + px-cache/README.md | 54 ++++++++++++++++++++++++++++++++ px-camoufox/Cargo.toml | 1 + px-camoufox/README.md | 60 +++++++++++++++++++++++++++++++++++ px-captcha/Cargo.toml | 1 + px-captcha/README.md | 43 ++++++++++++++++++++++++++ px-cli/README.md | 53 +++++++++++++++++++++++++++++++ px-cloudflare/Cargo.toml | 1 + px-cloudflare/README.md | 50 ++++++++++++++++++++++++++++++ px-core/Cargo.toml | 1 + px-core/README.md | 57 ++++++++++++++++++++++++++++++++++ px-datadome/Cargo.toml | 1 + px-datadome/README.md | 40 ++++++++++++++++++++++++ px-detector/Cargo.toml | 1 + px-detector/README.md | 48 ++++++++++++++++++++++++++++ px-errors/Cargo.toml | 1 + px-errors/README.md | 48 ++++++++++++++++++++++++++++ px-harvester/Cargo.toml | 1 + px-harvester/README.md | 59 +++++++++++++++++++++++++++++++++++ px-native/Cargo.toml | 1 + px-native/README.md | 61 ++++++++++++++++++++++++++++++++++++ px-perimeterx/Cargo.toml | 1 + px-perimeterx/README.md | 52 +++++++++++++++++++++++++++++++ px-pipeline/Cargo.toml | 1 + px-pipeline/README.md | 67 ++++++++++++++++++++++++++++++++++++++++ px-server/README.md | 61 ++++++++++++++++++++++++++++++++++++ px-turnstile/Cargo.toml | 1 + px-turnstile/README.md | 44 ++++++++++++++++++++++++++ px-types/Cargo.toml | 1 + px-types/README.md | 48 ++++++++++++++++++++++++++++ px-validation/Cargo.toml | 1 + px-validation/README.md | 49 +++++++++++++++++++++++++++++ xtask/README.md | 37 ++++++++++++++++++++++ 36 files changed, 1025 insertions(+), 4 deletions(-) create mode 100644 px-auth/README.md create mode 100644 px-cache/README.md create mode 100644 px-camoufox/README.md create mode 100644 px-captcha/README.md create mode 100644 px-cli/README.md create mode 100644 px-cloudflare/README.md create mode 100644 px-core/README.md create mode 100644 px-datadome/README.md create mode 100644 px-detector/README.md create mode 100644 px-errors/README.md create mode 100644 px-harvester/README.md create mode 100644 px-native/README.md create mode 100644 px-perimeterx/README.md create mode 100644 px-pipeline/README.md create mode 100644 px-server/README.md create mode 100644 px-turnstile/README.md create mode 100644 px-types/README.md create mode 100644 px-validation/README.md create mode 100644 xtask/README.md diff --git a/README.md b/README.md index 69d095d..1834a4b 100644 --- a/README.md +++ b/README.md @@ -28,13 +28,34 @@ cd px-solver cargo build --release ``` -From crates.io: +That builds both binaries — `px-server` and `px-cli`. They are **not** on crates.io (`publish = false`); source is the only way to get them. -```bash -cargo install pxsolver-server pxsolver-cli +From crates.io, as libraries: + +```toml +[dependencies] +pxsolver-core = "1.9" # domain types: SolveRequest, PxCookieBundle, CacheKey +pxsolver-pipeline = "1.9" # ChallengeHandler, SolveAction, Pipeline +pxsolver-harvester = "1.9" # Harvester port + stealth Chromium pool ``` -The 16 `pxsolver-*` library crates are also published individually for downstream Rust users; in source the workspace exposes them under the short `px-*` aliases. +All 16 `pxsolver-*` library crates are published individually, so you can depend on one piece +without the workspace. Each has its own README on crates.io. + +> **Package name ≠ crate name.** 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. + +| Crate | Purpose | +|---|---| +| [`pxsolver-core`](https://crates.io/crates/pxsolver-core) | Pure domain types, no I/O | +| [`pxsolver-types`](https://crates.io/crates/pxsolver-types) · [`pxsolver-errors`](https://crates.io/crates/pxsolver-errors) · [`pxsolver-validation`](https://crates.io/crates/pxsolver-validation) | Response envelope, `AppError`, `Validated` | +| [`pxsolver-pipeline`](https://crates.io/crates/pxsolver-pipeline) | `ChallengeHandler` port + ordered pipeline | +| [`pxsolver-detector`](https://crates.io/crates/pxsolver-detector) | PerimeterX detection from HTML/JS | +| [`pxsolver-harvester`](https://crates.io/crates/pxsolver-harvester) · [`pxsolver-camoufox`](https://crates.io/crates/pxsolver-camoufox) | Chromium pool · Camoufox/geckodriver pool | +| [`pxsolver-perimeterx`](https://crates.io/crates/pxsolver-perimeterx) · [`pxsolver-cloudflare`](https://crates.io/crates/pxsolver-cloudflare) | Shipping handlers | +| [`pxsolver-turnstile`](https://crates.io/crates/pxsolver-turnstile) · [`pxsolver-captcha`](https://crates.io/crates/pxsolver-captcha) · [`pxsolver-datadome`](https://crates.io/crates/pxsolver-datadome) | Detection-only stubs | +| [`pxsolver-native`](https://crates.io/crates/pxsolver-native) | Native `_px3` sensor synthesis | +| [`pxsolver-auth`](https://crates.io/crates/pxsolver-auth) · [`pxsolver-cache`](https://crates.io/crates/pxsolver-cache) | API keys + allowlist + audit · cookie cache | ## Quickstart diff --git a/px-auth/Cargo.toml b/px-auth/Cargo.toml index eb33ae3..de47b2b 100644 --- a/px-auth/Cargo.toml +++ b/px-auth/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "pxsolver-auth" description = "API-key + per-domain allowlist + audit log" +readme = "README.md" version.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/px-auth/README.md b/px-auth/README.md new file mode 100644 index 0000000..b69981e --- /dev/null +++ b/px-auth/README.md @@ -0,0 +1,53 @@ +# pxsolver-auth + +API keys, per-domain allowlist and audit log — the guardrails around a dual-use tool. + +Part of [**px-solver**](https://github.com/KeyCode17/px-solver) — a Rust solver service for +PerimeterX (HUMAN Security). Published standalone so you can depend on the one piece you need +instead of the whole workspace. + +## Install + +```toml +[dependencies] +pxsolver-auth = "1.9" +``` + +> **The package name and the crate name differ.** You depend on `pxsolver-auth`, but you `use px_auth::…`. +> The `pxsolver-` prefix exists to namespace the family on crates.io; the source-level name stays +> short. + +## What's in it + +```rust +use px_auth::{CheckAllowlist, VerifyKey, YamlKeyStore, YamlAllowlistStore}; + +verify_key.execute(id, secret).await?; // argon2, constant-time +check_allowlist.execute(&domain).await?; // explicit entry required +``` + +| Item | Purpose | +|---|---| +| `VerifyKey` / `KeyStore` / `ApiKeyRecord` | Argon2-hashed API keys, `id:secret` form | +| `CheckAllowlist` / `AllowlistStore` / `AllowlistEntry` | Per-domain allowlist, `tos_reviewed` enforced | +| `AuditSink` / `AuditEvent` / `AuditOutcome` | Append-only record of who solved what, when | +| `YamlKeyStore` / `YamlAllowlistStore` | File-backed stores | +| `FileAuditSink` / `StdoutAuditSink` | Audit destinations | + +## Why this exists + +px-solver is dual-use. Every request needs a key, every target needs an allowlist entry with +`tos_reviewed: true` and a non-empty justification, and the server **fails to start** if an entry +is missing either. See +[ADR-0007](https://github.com/KeyCode17/px-solver/blob/main/docs/adr/0007-api-key-and-domain-allowlist-guardrails.md). + +## Dual use + +px-solver is built for authorized testing against targets its operator controls or has permission +to test. The server enforces an API key and a per-domain allowlist requiring explicit +`tos_reviewed: true`. See +[`docs/dual-use-policy.md`](https://github.com/KeyCode17/px-solver/blob/main/docs/dual-use-policy.md). + +## License + +AGPL-3.0-or-later — chosen to discourage closed-source resale as an anonymous SaaS. diff --git a/px-cache/Cargo.toml b/px-cache/Cargo.toml index 8dad60b..dcfa8d4 100644 --- a/px-cache/Cargo.toml +++ b/px-cache/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "pxsolver-cache" description = "Cookie cache port + DashMap default, optional Redis adapter" +readme = "README.md" version.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/px-cache/README.md b/px-cache/README.md new file mode 100644 index 0000000..0cbbb06 --- /dev/null +++ b/px-cache/README.md @@ -0,0 +1,54 @@ +# pxsolver-cache + +The cookie-bundle cache port, with a DashMap-backed in-memory implementation. + +Part of [**px-solver**](https://github.com/KeyCode17/px-solver) — a Rust solver service for +PerimeterX (HUMAN Security). Published standalone so you can depend on the one piece you need +instead of the whole workspace. + +## Install + +```toml +[dependencies] +pxsolver-cache = "1.9" +``` + +> **The package name and the crate name differ.** You depend on `pxsolver-cache`, but you `use px_cache::…`. +> The `pxsolver-` prefix exists to namespace the family on crates.io; the source-level name stays +> short. + +## What's in it + +```rust +use px_cache::{CookieCache, InMemoryCookieCache}; + +let cache = InMemoryCookieCache::default(); +if let Some(bundle) = cache.get(&key).await? { + // still within the bundle's own expiry +} +``` + +| Item | Purpose | +|---|---| +| `CookieCache` | The port: `get` / `put`, async, object-safe | +| `InMemoryCookieCache` | Default implementation over `DashMap`, evicting on bundle expiry | +| `CacheMetrics` | Hit / miss / eviction counters | + +## Notes + +Entries expire with the bundle itself rather than on a separate timer — a cached `_px3` that has +outlived its own `expires_at` is never served. + +The key (`px_core::CacheKey`) includes the egress, so bundles earned through different proxies do +not collide. See [ADR-0025](https://github.com/KeyCode17/px-solver/blob/main/docs/adr/0025-egress-proxy-propagation-contract.md). + +## Dual use + +px-solver is built for authorized testing against targets its operator controls or has permission +to test. The server enforces an API key and a per-domain allowlist requiring explicit +`tos_reviewed: true`. See +[`docs/dual-use-policy.md`](https://github.com/KeyCode17/px-solver/blob/main/docs/dual-use-policy.md). + +## License + +AGPL-3.0-or-later — chosen to discourage closed-source resale as an anonymous SaaS. diff --git a/px-camoufox/Cargo.toml b/px-camoufox/Cargo.toml index d694143..d15e40e 100644 --- a/px-camoufox/Cargo.toml +++ b/px-camoufox/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "pxsolver-camoufox" description = "Camoufox (patched-Firefox) harvester via geckodriver + fantoccini (ADR-0020)" +readme = "README.md" version.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/px-camoufox/README.md b/px-camoufox/README.md new file mode 100644 index 0000000..e366275 --- /dev/null +++ b/px-camoufox/README.md @@ -0,0 +1,60 @@ +# pxsolver-camoufox + +Camoufox (patched Firefox) harvester and fetcher, driven over geckodriver. + +Part of [**px-solver**](https://github.com/KeyCode17/px-solver) — a Rust solver service for +PerimeterX (HUMAN Security). Published standalone so you can depend on the one piece you need +instead of the whole workspace. + +## Install + +```toml +[dependencies] +pxsolver-camoufox = "1.9" +``` + +> **The package name and the crate name differ.** You depend on `pxsolver-camoufox`, but you `use px_camoufox::…`. +> The `pxsolver-` prefix exists to namespace the family on crates.io; the source-level name stays +> short. + +## What's in it + +```rust +use px_camoufox::{CamoufoxConfig, CamoufoxPool}; + +let pool = CamoufoxPool::new(CamoufoxConfig::from_env())?; +``` + +| Item | Purpose | +|---|---| +| `CamoufoxPool` | Implements both `Harvester` and `Fetcher` over geckodriver + Camoufox | +| `CamoufoxConfig` | Binary paths, locale, headless, timeouts, concurrency — `from_env` | +| `capture_sensor` | XHR-hook capture of live PX sensor payloads, for calibration | + +Warm `PersistentSession`s are kept per domain for `/v1/fetch`, so repeat requests reuse a browser +that already holds a coherent cookie jar. + +## Egress rotation + +Set `PX_PROXIES` to a CSV list and each session takes one round-robin **at spawn**, holding it for +the session's 300s TTL. Distinct egress IPs per domain is therefore +`min(PX_FETCH_MAX_PER_DOMAIN, len(PX_PROXIES))` — not the product. + +Per-request harvests use the proxy named on the `HarvestRequest`; they do not draw from the +rotation, because a bundle bound to an IP the caller cannot name is not usable. + +## Requires operator-installed binaries + +Camoufox and geckodriver are **not** vendored — install them and point `CamoufoxConfig` at them +([ADR-0020](https://github.com/KeyCode17/px-solver/blob/main/docs/adr/0020-adopt-camoufox-via-fantoccini-geckodriver.md)). + +## Dual use + +px-solver is built for authorized testing against targets its operator controls or has permission +to test. The server enforces an API key and a per-domain allowlist requiring explicit +`tos_reviewed: true`. See +[`docs/dual-use-policy.md`](https://github.com/KeyCode17/px-solver/blob/main/docs/dual-use-policy.md). + +## License + +AGPL-3.0-or-later — chosen to discourage closed-source resale as an anonymous SaaS. diff --git a/px-captcha/Cargo.toml b/px-captcha/Cargo.toml index 20d1b60..9f15945 100644 --- a/px-captcha/Cargo.toml +++ b/px-captcha/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "pxsolver-captcha" description = "hCaptcha / reCAPTCHA gateway — STUB v1 (ADR-0015)" +readme = "README.md" version.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/px-captcha/README.md b/px-captcha/README.md new file mode 100644 index 0000000..39a14c4 --- /dev/null +++ b/px-captcha/README.md @@ -0,0 +1,43 @@ +# pxsolver-captcha + +hCaptcha / reCAPTCHA handler — detection only, solve is a stub. + +Part of [**px-solver**](https://github.com/KeyCode17/px-solver) — a Rust solver service for +PerimeterX (HUMAN Security). Published standalone so you can depend on the one piece you need +instead of the whole workspace. + +## Install + +```toml +[dependencies] +pxsolver-captcha = "1.9" +``` + +> **The package name and the crate name differ.** You depend on `pxsolver-captcha`, but you `use px_captcha::…`. +> The `pxsolver-` prefix exists to namespace the family on crates.io; the source-level name stays +> short. + +## Status: stub + +`CaptchaHandler` **detects** hCaptcha and reCAPTCHA (`hcaptcha.com/1/api.js`, +`recaptcha/api.js`, the `h-captcha` / `g-recaptcha` classes) and returns +`HandlerStatus::NotImplemented` from `solve` +([ADR-0015](https://github.com/KeyCode17/px-solver/blob/main/docs/adr/0015-v1-ships-pipeline-with-perimeterx-handler-only.md)). + +```rust +use px_captcha::CaptchaHandler; +let handler = CaptchaHandler::new(); +``` + +px-solver does not integrate a human-solving service, and this crate will not become one. + +## Dual use + +px-solver is built for authorized testing against targets its operator controls or has permission +to test. The server enforces an API key and a per-domain allowlist requiring explicit +`tos_reviewed: true`. See +[`docs/dual-use-policy.md`](https://github.com/KeyCode17/px-solver/blob/main/docs/dual-use-policy.md). + +## License + +AGPL-3.0-or-later — chosen to discourage closed-source resale as an anonymous SaaS. diff --git a/px-cli/README.md b/px-cli/README.md new file mode 100644 index 0000000..86a3a9b --- /dev/null +++ b/px-cli/README.md @@ -0,0 +1,53 @@ +# px-cli + +Operator CLI for px-solver — key generation, allowlist editing, solving, and sensor calibration. + +Part of [**px-solver**](https://github.com/KeyCode17/px-solver). + +## Not on crates.io + +This crate is `publish = false`. Only the 16 `pxsolver-*` **library** crates are published; build +the CLI from source: + +```bash +git clone https://github.com/KeyCode17/px-solver +cd px-solver +cargo build --release -p px-cli +``` + +## Commands + +```bash +# generate an API key — paste the printed id + argon2_hash into config/keys.yaml +px-cli keys generate --id ops1 --note "first operator" + +# add an allowlist entry (justification is mandatory) +px-cli allowlist add --domain example.com --justification "internal price observability" + +# solve a target through a running px-server +px-cli solve https://example.com/ \ + --api-key ops1: \ + --proxy socks5://127.0.0.1:9050 + +# diff a live capture against the native sensor's default_batch +px-cli calibrate px-research/captures//.json +``` + +`--server` defaults to `http://127.0.0.1:8080` and reads `PX_SERVER_URL`; `--api-key` reads +`PX_API_KEY`. + +## `--proxy` + +The egress the **solve** harvests through — `scheme://host:port` for `http`, `https`, `socks5` or +`socks5h`. The returned bundle is bound to that IP, so send downstream requests through the same +proxy. Omit it to harvest from the server's own address. + +This is not `PX_PROXIES`, which is an operator-side rotation for `/v1/fetch` sessions only. See +[Egress proxies](../docs/deployment.md#egress-proxies). + +Browser engines cannot authenticate to a proxy, so `user:pass@` is stripped with a warning — +front an authenticated upstream with a local relay (gost, 3proxy). + +## License + +AGPL-3.0-or-later. diff --git a/px-cloudflare/Cargo.toml b/px-cloudflare/Cargo.toml index 770a6f6..0cec959 100644 --- a/px-cloudflare/Cargo.toml +++ b/px-cloudflare/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "pxsolver-cloudflare" description = "Cloudflare challenge handler (ADR-0015, R5.7) — delegates to CF-bypass harvester (e.g. CamoufoxPool)" +readme = "README.md" version.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/px-cloudflare/README.md b/px-cloudflare/README.md new file mode 100644 index 0000000..65252b4 --- /dev/null +++ b/px-cloudflare/README.md @@ -0,0 +1,50 @@ +# pxsolver-cloudflare + +Cloudflare interstitial handler, backed by a Camoufox harvester. + +Part of [**px-solver**](https://github.com/KeyCode17/px-solver) — a Rust solver service for +PerimeterX (HUMAN Security). Published standalone so you can depend on the one piece you need +instead of the whole workspace. + +## Install + +```toml +[dependencies] +pxsolver-cloudflare = "1.9" +``` + +> **The package name and the crate name differ.** You depend on `pxsolver-cloudflare`, but you `use px_cloudflare::…`. +> The `pxsolver-` prefix exists to namespace the family on crates.io; the source-level name stays +> short. + +## What's in it + +```rust +use px_cloudflare::CloudflareHandler; + +let handler = CloudflareHandler::with_harvester(camoufox_pool); +``` + +Detects `cdn-cgi/challenge-platform`, `cf-mitigated` and `cf_clearance` markers, re-harvests the +URL through the supplied harvester, and returns `cf_clearance` / `__cf_bm` plus any PX cookies the +same fetch happens to set. + +`extract_session_cookies` / `is_session_cookie` are exposed for reuse. + +## Notes + +Constructed without a harvester (`CloudflareHandler::new()`), `solve` returns `NotImplemented` +rather than pretending — detection still works. Pair it with +[`pxsolver-camoufox`](https://crates.io/crates/pxsolver-camoufox) for the bypass path +([ADR-0020](https://github.com/KeyCode17/px-solver/blob/main/docs/adr/0020-adopt-camoufox-via-fantoccini-geckodriver.md)). + +## Dual use + +px-solver is built for authorized testing against targets its operator controls or has permission +to test. The server enforces an API key and a per-domain allowlist requiring explicit +`tos_reviewed: true`. See +[`docs/dual-use-policy.md`](https://github.com/KeyCode17/px-solver/blob/main/docs/dual-use-policy.md). + +## License + +AGPL-3.0-or-later — chosen to discourage closed-source resale as an anonymous SaaS. diff --git a/px-core/Cargo.toml b/px-core/Cargo.toml index fdead31..e8930d3 100644 --- a/px-core/Cargo.toml +++ b/px-core/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "pxsolver-core" description = "Pure domain types shared across px-solver crates" +readme = "README.md" version.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/px-core/README.md b/px-core/README.md new file mode 100644 index 0000000..11b7540 --- /dev/null +++ b/px-core/README.md @@ -0,0 +1,57 @@ +# pxsolver-core + +Pure domain types shared across the px-solver crates — no I/O, no framework. + +Part of [**px-solver**](https://github.com/KeyCode17/px-solver) — a Rust solver service for +PerimeterX (HUMAN Security). Published standalone so you can depend on the one piece you need +instead of the whole workspace. + +## Install + +```toml +[dependencies] +pxsolver-core = "1.9" +``` + +> **The package name and the crate name differ.** You depend on `pxsolver-core`, but you `use px_core::…`. +> The `pxsolver-` prefix exists to namespace the family on crates.io; the source-level name stays +> short. + +## What's in it + +| Type | Purpose | +|---|---| +| `SolveRequest` | A solve to perform: `url`, optional `proxy`, optional `fingerprint` | +| `PxCookieBundle` / `NamedCookie` / `CookieJarDelta` | The `_px3` bundle a solve returns, with expiry | +| `CacheKey` | Domain + app id + fingerprint/egress key | +| `PxAppId` | Validated PerimeterX app id (`PX` + 8 chars) | +| `Fingerprint` | Browser fingerprint the solver presents | +| `ApiKeyHash`, `NamedToken`, `PxDetection`, `SolveOutcome` | Supporting value types | + +## Example + +```rust +use px_core::SolveRequest; + +// Name the egress you will send downstream traffic through: a _px3 bundle is +// bound to the IP that earned it. +let request = SolveRequest::new("https://example.com/") + .with_proxy("socks5://127.0.0.1:9050"); +``` + +Absent optionals are omitted from the wire rather than serialized as `null`. + +## Notes + +This crate has no dependency on any other px-solver crate — everything else depends on it. + +## Dual use + +px-solver is built for authorized testing against targets its operator controls or has permission +to test. The server enforces an API key and a per-domain allowlist requiring explicit +`tos_reviewed: true`. See +[`docs/dual-use-policy.md`](https://github.com/KeyCode17/px-solver/blob/main/docs/dual-use-policy.md). + +## License + +AGPL-3.0-or-later — chosen to discourage closed-source resale as an anonymous SaaS. diff --git a/px-datadome/Cargo.toml b/px-datadome/Cargo.toml index 345e669..558fb13 100644 --- a/px-datadome/Cargo.toml +++ b/px-datadome/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "pxsolver-datadome" description = "DataDome handler — STUB v1 (ADR-0015)" +readme = "README.md" version.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/px-datadome/README.md b/px-datadome/README.md new file mode 100644 index 0000000..ecf755b --- /dev/null +++ b/px-datadome/README.md @@ -0,0 +1,40 @@ +# pxsolver-datadome + +DataDome handler — detection only, solve is a stub. + +Part of [**px-solver**](https://github.com/KeyCode17/px-solver) — a Rust solver service for +PerimeterX (HUMAN Security). Published standalone so you can depend on the one piece you need +instead of the whole workspace. + +## Install + +```toml +[dependencies] +pxsolver-datadome = "1.9" +``` + +> **The package name and the crate name differ.** You depend on `pxsolver-datadome`, but you `use px_datadome::…`. +> The `pxsolver-` prefix exists to namespace the family on crates.io; the source-level name stays +> short. + +## Status: stub + +`DataDomeHandler` **detects** DataDome (`DD_OPTIONS`, `datadome.co/captcha`, `ddg_datadome`) and +returns `HandlerStatus::NotImplemented` from `solve` +([ADR-0015](https://github.com/KeyCode17/px-solver/blob/main/docs/adr/0015-v1-ships-pipeline-with-perimeterx-handler-only.md)). + +```rust +use px_datadome::DataDomeHandler; +let handler = DataDomeHandler::new(); +``` + +## Dual use + +px-solver is built for authorized testing against targets its operator controls or has permission +to test. The server enforces an API key and a per-domain allowlist requiring explicit +`tos_reviewed: true`. See +[`docs/dual-use-policy.md`](https://github.com/KeyCode17/px-solver/blob/main/docs/dual-use-policy.md). + +## License + +AGPL-3.0-or-later — chosen to discourage closed-source resale as an anonymous SaaS. diff --git a/px-detector/Cargo.toml b/px-detector/Cargo.toml index b8f03b5..b3652a6 100644 --- a/px-detector/Cargo.toml +++ b/px-detector/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "pxsolver-detector" description = "PerimeterX detection (HTML, JS globals, block-page)" +readme = "README.md" version.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/px-detector/README.md b/px-detector/README.md new file mode 100644 index 0000000..93578f1 --- /dev/null +++ b/px-detector/README.md @@ -0,0 +1,48 @@ +# pxsolver-detector + +PerimeterX detection from HTML, JS globals and block-page markers. + +Part of [**px-solver**](https://github.com/KeyCode17/px-solver) — a Rust solver service for +PerimeterX (HUMAN Security). Published standalone so you can depend on the one piece you need +instead of the whole workspace. + +## Install + +```toml +[dependencies] +pxsolver-detector = "1.9" +``` + +> **The package name and the crate name differ.** You depend on `pxsolver-detector`, but you `use px_detector::…`. +> The `pxsolver-` prefix exists to namespace the family on crates.io; the source-level name stays +> short. + +## What's in it + +```rust +use px_detector::{Detected, Detector, RegexDetector}; + +match RegexDetector::new().detect(&html) { + Detected::Yes(app_id) => { /* PX present, app id recovered when exposed */ } + Detected::No => { /* no PX markers */ } +} +``` + +`RegexDetector` looks for the captcha/challenge scaffolding, `_px*` cookie names, the +`window._pxAppId` family of globals, and the block-page signatures — returning the app id when +the page exposes one. + +## Notes + +Detection is content-only and does no I/O, so it is cheap enough to run on every fetched page. + +## Dual use + +px-solver is built for authorized testing against targets its operator controls or has permission +to test. The server enforces an API key and a per-domain allowlist requiring explicit +`tos_reviewed: true`. See +[`docs/dual-use-policy.md`](https://github.com/KeyCode17/px-solver/blob/main/docs/dual-use-policy.md). + +## License + +AGPL-3.0-or-later — chosen to discourage closed-source resale as an anonymous SaaS. diff --git a/px-errors/Cargo.toml b/px-errors/Cargo.toml index 50c9a70..2d96bdf 100644 --- a/px-errors/Cargo.toml +++ b/px-errors/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "pxsolver-errors" description = "Centralized AppError + IntoResponse for px-solver" +readme = "README.md" version.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/px-errors/README.md b/px-errors/README.md new file mode 100644 index 0000000..faf4743 --- /dev/null +++ b/px-errors/README.md @@ -0,0 +1,48 @@ +# pxsolver-errors + +One `AppError` for the whole workspace, with its Axum `IntoResponse`. + +Part of [**px-solver**](https://github.com/KeyCode17/px-solver) — a Rust solver service for +PerimeterX (HUMAN Security). Published standalone so you can depend on the one piece you need +instead of the whole workspace. + +## Install + +```toml +[dependencies] +pxsolver-errors = "1.9" +``` + +> **The package name and the crate name differ.** You depend on `pxsolver-errors`, but you `use px_errors::…`. +> The `pxsolver-` prefix exists to namespace the family on crates.io; the source-level name stays +> short. + +## What's in it + +```rust +use px_errors::AppError; + +fn parse(url: &str) -> Result<(), AppError> { + Err(AppError::BadRequest("Invalid url".into())) +} +``` + +`AppError` covers `BadRequest`, `Unauthorized`, `Forbidden`, `NotFound`, `Conflict`, +`ValidationError` and `InternalError`, each mapping to its status code and to a stable +machine-readable code (`bad_request`, `internal_error`, …) in the serialized `ErrorResponse`. + +## Notes + +The payload string **is** the user-facing message — it is returned verbatim, so it starts a +sentence and carries no internal detail. Diagnostics belong in the logs. + +## Dual use + +px-solver is built for authorized testing against targets its operator controls or has permission +to test. The server enforces an API key and a per-domain allowlist requiring explicit +`tos_reviewed: true`. See +[`docs/dual-use-policy.md`](https://github.com/KeyCode17/px-solver/blob/main/docs/dual-use-policy.md). + +## License + +AGPL-3.0-or-later — chosen to discourage closed-source resale as an anonymous SaaS. diff --git a/px-harvester/Cargo.toml b/px-harvester/Cargo.toml index ddc9d4a..3e7e83c 100644 --- a/px-harvester/Cargo.toml +++ b/px-harvester/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "pxsolver-harvester" description = "Vendor-agnostic Chromium pool + stealth core (chromiumoxide)" +readme = "README.md" version.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/px-harvester/README.md b/px-harvester/README.md new file mode 100644 index 0000000..e9ff6da --- /dev/null +++ b/px-harvester/README.md @@ -0,0 +1,59 @@ +# pxsolver-harvester + +The vendor-agnostic `Harvester` port plus a stealth-patched Chromium pool. + +Part of [**px-solver**](https://github.com/KeyCode17/px-solver) — a Rust solver service for +PerimeterX (HUMAN Security). Published standalone so you can depend on the one piece you need +instead of the whole workspace. + +## Install + +```toml +[dependencies] +pxsolver-harvester = "1.9" +``` + +> **The package name and the crate name differ.** You depend on `pxsolver-harvester`, but you `use px_harvester::…`. +> The `pxsolver-` prefix exists to namespace the family on crates.io; the source-level name stays +> short. + +## What's in it + +```rust +use px_harvester::{ChromiumoxidePool, HarvestRequest, Harvester, PoolConfig}; + +let pool = ChromiumoxidePool::new(PoolConfig::default()); +let result = pool + .harvest(HarvestRequest::new("https://example.com/") + .with_proxy(Some("socks5://127.0.0.1:9050".into()))) + .await?; +``` + +| Item | Purpose | +|---|---| +| `Harvester` | The port: drive a real browser at a URL, return HTML + UA + cookies | +| `ChromiumoxidePool` | `chromiumoxide` implementation with a concurrency semaphore and CDP stealth patches | +| `HarvestRequest` / `HarvestResult` | What to harvest and what came back | +| `StealthBundle` | The injected `evaluate_on_new_document` payload | +| `strip_credentials` | Drops `user:pass@` from a proxy URL, with a warning | + +## Proxy credentials do not work in a browser + +geckodriver's W3C `proxy` capability has no credential field, and Chromium's `--proxy-server` +ignores userinfo without a CDP `Fetch.authRequired` handler. Rather than fail silently, +`strip_credentials` removes them and logs the sanitized URL. Front an authenticated upstream with +a local unauthenticated relay (gost, 3proxy). + +`socks5h://` is normalized to `socks5://` for Chromium, which has no such scheme and would +otherwise ignore the spec and go direct. + +## Dual use + +px-solver is built for authorized testing against targets its operator controls or has permission +to test. The server enforces an API key and a per-domain allowlist requiring explicit +`tos_reviewed: true`. See +[`docs/dual-use-policy.md`](https://github.com/KeyCode17/px-solver/blob/main/docs/dual-use-policy.md). + +## License + +AGPL-3.0-or-later — chosen to discourage closed-source resale as an anonymous SaaS. diff --git a/px-native/Cargo.toml b/px-native/Cargo.toml index 7ed5412..153de29 100644 --- a/px-native/Cargo.toml +++ b/px-native/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "pxsolver-native" description = "Native PerimeterX sensor generator — N1 cipher core (ADR-0024)" +readme = "README.md" version.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/px-native/README.md b/px-native/README.md new file mode 100644 index 0000000..b7fccd4 --- /dev/null +++ b/px-native/README.md @@ -0,0 +1,61 @@ +# pxsolver-native + +Native `_px3` sensor synthesis — build the payload locally instead of driving a browser. + +Part of [**px-solver**](https://github.com/KeyCode17/px-solver) — a Rust solver service for +PerimeterX (HUMAN Security). Published standalone so you can depend on the one piece you need +instead of the whole workspace. + +## Install + +```toml +[dependencies] +pxsolver-native = "1.9" +``` + +> **The package name and the crate name differ.** You depend on `pxsolver-native`, but you `use px_native::…`. +> The `pxsolver-` prefix exists to namespace the family on crates.io; the source-level name stays +> short. + +## What's in it + +```rust +use px_native::{NativeSolver, SensorNativeSolver, SolveContext}; +use px_native::profile::TenantProfile; + +let solver = SensorNativeSolver::new(client, Arc::new(TenantProfile::load(path)?)); +let ctx = SolveContext::new(url, app_id, fingerprint) + .with_proxy(Some("socks5://127.0.0.1:9050".into())); +let bundle = solver.solve(&ctx).await?; +``` + +| Item | Purpose | +|---|---| +| `NativeSolver` | The port: context in, `PxCookieBundle` out | +| `SensorNativeSolver` | Builds the sensor payload, POSTs it, parses `Set-Cookie` | +| `cipher` | The `vP` encryption of the sensor batch | +| `events` | The `[{t, d}, …]` event grammar (`default_batch`) | +| `profile::TenantProfile` | Per-tenant app id, sensor path and fallbacks | +| `NativeFirstHandler` | Decorator: try native, fall back to a browser handler on failure | + +## Notes + +This path is **orders of magnitude cheaper** than a browser — no process spawn, one HTTP POST. Its +accuracy depends on `default_batch` matching the tenant's real event grammar, which is calibrated +against captures; see +[`docs/runbook-native-bypass.md`](https://github.com/KeyCode17/px-solver/blob/main/docs/runbook-native-bypass.md) +and [ADR-0024](https://github.com/KeyCode17/px-solver/blob/main/docs/adr/0024-activate-native-px3-sensor-synthesis.md). + +Unlike the browser paths, this one runs on `reqwest` and **does** support proxy authentication, so +`user:pass@` in the proxy URL works here. + +## Dual use + +px-solver is built for authorized testing against targets its operator controls or has permission +to test. The server enforces an API key and a per-domain allowlist requiring explicit +`tos_reviewed: true`. See +[`docs/dual-use-policy.md`](https://github.com/KeyCode17/px-solver/blob/main/docs/dual-use-policy.md). + +## License + +AGPL-3.0-or-later — chosen to discourage closed-source resale as an anonymous SaaS. diff --git a/px-perimeterx/Cargo.toml b/px-perimeterx/Cargo.toml index 1f177f2..0b7de3a 100644 --- a/px-perimeterx/Cargo.toml +++ b/px-perimeterx/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "pxsolver-perimeterx" description = "PerimeterX challenge handler (v1 marquee)" +readme = "README.md" version.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/px-perimeterx/README.md b/px-perimeterx/README.md new file mode 100644 index 0000000..d00afc3 --- /dev/null +++ b/px-perimeterx/README.md @@ -0,0 +1,52 @@ +# pxsolver-perimeterx + +The PerimeterX `ChallengeHandler` — px-solver's marquee path. + +Part of [**px-solver**](https://github.com/KeyCode17/px-solver) — a Rust solver service for +PerimeterX (HUMAN Security). Published standalone so you can depend on the one piece you need +instead of the whole workspace. + +## Install + +```toml +[dependencies] +pxsolver-perimeterx = "1.9" +``` + +> **The package name and the crate name differ.** You depend on `pxsolver-perimeterx`, but you `use px_perimeterx::…`. +> The `pxsolver-` prefix exists to namespace the family on crates.io; the source-level name stays +> short. + +## What's in it + +```rust +use px_perimeterx::PerimeterxHandler; + +let handler = PerimeterxHandler::new(harvester); +``` + +| Item | Purpose | +|---|---| +| `PerimeterxHandler` | Detects PX, harvests through a real browser, extracts the `_px*` bundle | +| `SolvePx` | The use case behind it, usable without the pipeline | +| `PxHd` / `PxHdUrl` / `Sid` | Parsed PX identifiers with their own error types | + +## Notes + +This handler defeats PX by **avoidance** — a real browser passes the challenge legitimately and +the resulting cookies are extracted. Native sensor synthesis lives in +[`pxsolver-native`](https://crates.io/crates/pxsolver-native). + +The request's proxy is forwarded into the harvest, so the returned bundle is bound to the IP the +caller named. + +## Dual use + +px-solver is built for authorized testing against targets its operator controls or has permission +to test. The server enforces an API key and a per-domain allowlist requiring explicit +`tos_reviewed: true`. See +[`docs/dual-use-policy.md`](https://github.com/KeyCode17/px-solver/blob/main/docs/dual-use-policy.md). + +## License + +AGPL-3.0-or-later — chosen to discourage closed-source resale as an anonymous SaaS. diff --git a/px-pipeline/Cargo.toml b/px-pipeline/Cargo.toml index 9160726..99d392c 100644 --- a/px-pipeline/Cargo.toml +++ b/px-pipeline/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "pxsolver-pipeline" description = "ChallengeHandler trait + ordered pipeline orchestrator (ADR-0014)" +readme = "README.md" version.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/px-pipeline/README.md b/px-pipeline/README.md new file mode 100644 index 0000000..125d9f7 --- /dev/null +++ b/px-pipeline/README.md @@ -0,0 +1,67 @@ +# pxsolver-pipeline + +The `ChallengeHandler` port and the ordered pipeline that routes a page to the right vendor handler. + +Part of [**px-solver**](https://github.com/KeyCode17/px-solver) — a Rust solver service for +PerimeterX (HUMAN Security). Published standalone so you can depend on the one piece you need +instead of the whole workspace. + +## Install + +```toml +[dependencies] +pxsolver-pipeline = "1.9" +``` + +> **The package name and the crate name differ.** You depend on `pxsolver-pipeline`, but you `use px_pipeline::…`. +> The `pxsolver-` prefix exists to namespace the family on crates.io; the source-level name stays +> short. + +## What's in it + +```rust +use px_pipeline::{ChallengeHandler, PageHtml, Pipeline, SolveAction}; + +let pipeline = Pipeline::new(vec![perimeterx, cloudflare]); +let action = SolveAction::new(PageHtml::new(url, html)) + .with_proxy(Some("socks5://127.0.0.1:9050".into())); +let outcomes = pipeline.run(&action).await?; +``` + +| Item | Purpose | +|---|---| +| `ChallengeHandler` | `detects(&PageHtml)` + `solve(&SolveAction)` — one impl per protection vendor | +| `SolveAction` | What to solve: the page plus the egress proxy for this request | +| `Pipeline` | Runs handlers in order, stopping at the first solve (configurable) | +| `HandlerOutcome` / `HandlerStatus` | Solved / Skipped / NotImplemented, with metrics | +| `Fetcher` | Port for running one arbitrary HTTP request inside a handler-owned browser session | + +## Notes + +`detects` sees only the page — detection is content-only. `solve` additionally receives the +egress, because that is a property of the request rather than of the page. + +## v1.9.0 is source-breaking + +`ChallengeHandler::solve` and `SolveDispatcher::solve` take a `SolveAction` / `SolveRequest` +instead of `&PageHtml` / `&str`, so the per-request egress proxy can reach the browser: + +```rust +// before +async fn solve(&self, page: &PageHtml) -> Result +// after — action.page is the old argument, action.proxy the requested egress +async fn solve(&self, action: &SolveAction) -> Result +``` + +Rationale: [ADR-0025](https://github.com/KeyCode17/px-solver/blob/main/docs/adr/0025-egress-proxy-propagation-contract.md). + +## Dual use + +px-solver is built for authorized testing against targets its operator controls or has permission +to test. The server enforces an API key and a per-domain allowlist requiring explicit +`tos_reviewed: true`. See +[`docs/dual-use-policy.md`](https://github.com/KeyCode17/px-solver/blob/main/docs/dual-use-policy.md). + +## License + +AGPL-3.0-or-later — chosen to discourage closed-source resale as an anonymous SaaS. diff --git a/px-server/README.md b/px-server/README.md new file mode 100644 index 0000000..7f7d50f --- /dev/null +++ b/px-server/README.md @@ -0,0 +1,61 @@ +# px-server + +Composition root + Axum HTTP API — the px-solver service binary. + +Part of [**px-solver**](https://github.com/KeyCode17/px-solver). + +## Not on crates.io + +This crate is `publish = false`. Only the 16 `pxsolver-*` **library** crates are published; the +binaries are built from source: + +```bash +git clone https://github.com/KeyCode17/px-solver +cd px-solver +cargo build --release -p px-server +``` + +## Run + +```bash +PX_BIND=127.0.0.1:8080 \ +PX_KEYS=config/keys.yaml \ +PX_ALLOWLIST=config/allowlist.yaml \ +./target/release/px-server +``` + +Binding to `127.0.0.1` is the default and the recommended posture; front it with nginx/Caddy for +TLS. Full install, systemd unit and key rotation: +[`docs/deployment.md`](../docs/deployment.md). + +## Endpoints + +| Route | Purpose | +|---|---| +| `POST /v1/solve` | Solve a target, return the `_px3` cookie bundle | +| `POST /v1/fetch` | Run one HTTP request inside a warm browser session | +| `GET /health` | Status, build sha, uptime | +| `GET /metrics` | Solve counters, latency histogram, auth/allowlist denials | + +Every request needs `Authorization: Bearer :`, and every target needs an allowlist +entry with `tos_reviewed: true`. + +## Proxies + +`/v1/solve` takes a per-request `"proxy"`; `/v1/fetch` rotates the operator's `PX_PROXIES` list +across warm sessions. They are separate mechanisms and neither falls back to the other — a `_px3` +bundle is bound to the IP that earned it, so a rotating egress the caller cannot name would be +useless. The proxy is part of the cache key. + +See [Egress proxies](../docs/deployment.md#egress-proxies) and +[ADR-0025](../docs/adr/0025-egress-proxy-propagation-contract.md). + +## Layout + +Hexagonal: `application/` holds the dispatcher ports and use cases, `infrastructure/http/` the +handlers, DTOs and router, `infrastructure/bootstrap/` the composition root that wires state, +handlers and the native overlay. + +## License + +AGPL-3.0-or-later. diff --git a/px-turnstile/Cargo.toml b/px-turnstile/Cargo.toml index 0a3b8b1..3a895f5 100644 --- a/px-turnstile/Cargo.toml +++ b/px-turnstile/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "pxsolver-turnstile" description = "Cloudflare Turnstile handler — STUB v1 (ADR-0015)" +readme = "README.md" version.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/px-turnstile/README.md b/px-turnstile/README.md new file mode 100644 index 0000000..7cf12aa --- /dev/null +++ b/px-turnstile/README.md @@ -0,0 +1,44 @@ +# pxsolver-turnstile + +Cloudflare Turnstile handler — detection only, solve is a stub. + +Part of [**px-solver**](https://github.com/KeyCode17/px-solver) — a Rust solver service for +PerimeterX (HUMAN Security). Published standalone so you can depend on the one piece you need +instead of the whole workspace. + +## Install + +```toml +[dependencies] +pxsolver-turnstile = "1.9" +``` + +> **The package name and the crate name differ.** You depend on `pxsolver-turnstile`, but you `use px_turnstile::…`. +> The `pxsolver-` prefix exists to namespace the family on crates.io; the source-level name stays +> short. + +## Status: stub + +`TurnstileHandler` **detects** Turnstile (`challenges.cloudflare.com/turnstile`, `cf-turnstile`) +and returns `HandlerStatus::NotImplemented` from `solve`. It exists so the pipeline routes +correctly today and the real implementation slots in without a signature change +([ADR-0015](https://github.com/KeyCode17/px-solver/blob/main/docs/adr/0015-v1-ships-pipeline-with-perimeterx-handler-only.md)). + +```rust +use px_turnstile::TurnstileHandler; +let handler = TurnstileHandler::new(); +``` + +Turnstile's invisible mode is often **incidentally** passed by running a real stealth browser, but +no success rate is committed for it. + +## Dual use + +px-solver is built for authorized testing against targets its operator controls or has permission +to test. The server enforces an API key and a per-domain allowlist requiring explicit +`tos_reviewed: true`. See +[`docs/dual-use-policy.md`](https://github.com/KeyCode17/px-solver/blob/main/docs/dual-use-policy.md). + +## License + +AGPL-3.0-or-later — chosen to discourage closed-source resale as an anonymous SaaS. diff --git a/px-types/Cargo.toml b/px-types/Cargo.toml index 3aa135a..ba8ddf9 100644 --- a/px-types/Cargo.toml +++ b/px-types/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "pxsolver-types" description = "Shared response shapes (SingleResponse, ListResponse, PaginationMeta)" +readme = "README.md" version.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/px-types/README.md b/px-types/README.md new file mode 100644 index 0000000..8d026b6 --- /dev/null +++ b/px-types/README.md @@ -0,0 +1,48 @@ +# pxsolver-types + +The HTTP response envelope every px-solver route returns. + +Part of [**px-solver**](https://github.com/KeyCode17/px-solver) — a Rust solver service for +PerimeterX (HUMAN Security). Published standalone so you can depend on the one piece you need +instead of the whole workspace. + +## Install + +```toml +[dependencies] +pxsolver-types = "1.9" +``` + +> **The package name and the crate name differ.** You depend on `pxsolver-types`, but you `use px_types::…`. +> The `pxsolver-` prefix exists to namespace the family on crates.io; the source-level name stays +> short. + +## What's in it + +```rust +use px_types::{SingleResponse, ListResponse, PaginationMeta}; + +// { "data": …, "status": "solved" } +let body = SingleResponse::new(payload, "solved"); +``` + +| Type | Shape | +|---|---| +| `SingleResponse` | `{ data, status }` | +| `ListResponse` | `{ data, meta, status }` | +| `PaginationMeta` | `{ page, per_page, total }` | + +## Notes + +Wire keys are snake_case. Every route returns one of these — there are no bare payloads. + +## Dual use + +px-solver is built for authorized testing against targets its operator controls or has permission +to test. The server enforces an API key and a per-domain allowlist requiring explicit +`tos_reviewed: true`. See +[`docs/dual-use-policy.md`](https://github.com/KeyCode17/px-solver/blob/main/docs/dual-use-policy.md). + +## License + +AGPL-3.0-or-later — chosen to discourage closed-source resale as an anonymous SaaS. diff --git a/px-validation/Cargo.toml b/px-validation/Cargo.toml index 0e651ab..51cc61c 100644 --- a/px-validation/Cargo.toml +++ b/px-validation/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "pxsolver-validation" description = "Validated Axum extractor + Validate trait" +readme = "README.md" version.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/px-validation/README.md b/px-validation/README.md new file mode 100644 index 0000000..41003be --- /dev/null +++ b/px-validation/README.md @@ -0,0 +1,49 @@ +# pxsolver-validation + +A `Validated` Axum extractor plus the `Validate` trait behind it. + +Part of [**px-solver**](https://github.com/KeyCode17/px-solver) — a Rust solver service for +PerimeterX (HUMAN Security). Published standalone so you can depend on the one piece you need +instead of the whole workspace. + +## Install + +```toml +[dependencies] +pxsolver-validation = "1.9" +``` + +> **The package name and the crate name differ.** You depend on `pxsolver-validation`, but you `use px_validation::…`. +> The `pxsolver-` prefix exists to namespace the family on crates.io; the source-level name stays +> short. + +## What's in it + +```rust +use px_validation::{Validate, Validated}; + +impl Validate for CreateThing { + fn validate(&self) -> Result<(), AppError> { /* … */ Ok(()) } +} + +async fn handler(Validated(body): Validated) -> impl IntoResponse { /* … */ } +``` + +Deserialization and validation both happen at the edge, so handlers receive a value that is +already known good. + +## Notes + +Validation failures surface as `AppError::ValidationError` → HTTP 400 with the standard error +envelope. + +## Dual use + +px-solver is built for authorized testing against targets its operator controls or has permission +to test. The server enforces an API key and a per-domain allowlist requiring explicit +`tos_reviewed: true`. See +[`docs/dual-use-policy.md`](https://github.com/KeyCode17/px-solver/blob/main/docs/dual-use-policy.md). + +## License + +AGPL-3.0-or-later — chosen to discourage closed-source resale as an anonymous SaaS. diff --git a/xtask/README.md b/xtask/README.md new file mode 100644 index 0000000..fcc919b --- /dev/null +++ b/xtask/README.md @@ -0,0 +1,37 @@ +# xtask + +Dev automation for px-solver, run as `cargo xtask `. + +Part of [**px-solver**](https://github.com/KeyCode17/px-solver). `publish = false` — this is a +build-tool member, never a dependency. + +## Commands + +| Command | What it does | +|---|---| +| `cargo xtask bump ` | Rewrites `[workspace.package] version`, re-pins the internal `pxsolver-*` deps, runs the gate, commits and tags | +| `cargo xtask phase ` | Phase-aligned bump per [ADR-0017](../docs/adr/0017-phase-aligned-versioning.md) — minor for 00–03, major at 04, no-op for `R*` | +| `cargo xtask check-loc [--max N]` | The 200-LOC-per-file rule | +| `cargo xtask release [--remote]` | Pushes `HEAD` + tag, creates the GitHub release | +| `cargo xtask canary` | Live canary solve against an allowlisted target (gated on `CI_CANARY=1`) | +| `cargo xtask soak` | Throughput soak against a running server | + +## Why `bump` re-pins + +Every crate carries `version.workspace = true`, so all 16 published crates move in lockstep. The +`[workspace.dependencies]` entries pin those same crates by version for `cargo publish`, and if +they are left behind, a **major** bump stops resolving: `1.9.0` still satisfies `^1.4.0`, `2.0.0` +does not. `bump` rewrites both, so the drift cannot recur. + +## Release flow + +`bump` and `release` only push the tag. `.github/workflows/release.yml` takes it from there: a +GitHub release is created on any `v*.*.*` tag, while the crates.io publish is gated on the tagged +commit being on `main` **and** the bump being minor/major/initial. Patch tags never publish. + +Note that `release.yml` rewrites the GitHub release body from the commit log, so hand-written +release notes must be applied **after** the workflow finishes. + +## License + +AGPL-3.0-or-later.