Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>` |
| [`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

Expand Down
1 change: 1 addition & 0 deletions px-auth/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
53 changes: 53 additions & 0 deletions px-auth/README.md
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions px-cache/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
54 changes: 54 additions & 0 deletions px-cache/README.md
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions px-camoufox/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
60 changes: 60 additions & 0 deletions px-camoufox/README.md
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions px-captcha/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
43 changes: 43 additions & 0 deletions px-captcha/README.md
Original file line number Diff line number Diff line change
@@ -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.
53 changes: 53 additions & 0 deletions px-cli/README.md
Original file line number Diff line number Diff line change
@@ -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:<secret> \
--proxy socks5://127.0.0.1:9050

# diff a live capture against the native sensor's default_batch
px-cli calibrate px-research/captures/<tenant>/<ts>.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.
1 change: 1 addition & 0 deletions px-cloudflare/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
50 changes: 50 additions & 0 deletions px-cloudflare/README.md
Original file line number Diff line number Diff line change
@@ -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.
Loading
Loading