307 Commits
Author SHA1 Message Date
germondai cbf6749103 ci: expand container publishing workflows 2026-07-26 08:29:16 +02:00
germondai 48ccac75ee test: add workspace test command 2026-07-25 19:06:37 +02:00
germondai eff5760ef5 feat(api): wire proxy lifecycle and context cache 2026-07-25 17:42:11 +02:00
germondai e62e73554c feat(proxy): route traffic through tiered fallback 2026-07-25 15:13:58 +02:00
germondai d7cca3ec44 feat(proxy): add challenge-aware response policy 2026-07-25 12:47:26 +02:00
germondai ce028e201c feat(proxy): add adaptive direct forwarding 2026-07-25 10:22:39 +02:00
germondai 748d89dfc5 refactor(proxy): normalize proxy pool fallbacks 2026-07-25 08:34:12 +02:00
germondai 1d752aee92 feat(proxy): add transparent header sanitization 2026-07-24 18:19:44 +02:00
germondai fa2bcbb64f refactor(solvers): simplify optional results 2026-07-24 16:51:07 +02:00
germondai d1ebbddf38 feat(tiers): preserve raw response payloads 2026-07-24 14:26:31 +02:00
germondai defade4324 refactor(browser): simplify optional pool state 2026-07-24 12:08:55 +02:00
germondai 3e26a7e990 feat(browser): add persistent context cache 2026-07-24 10:43:18 +02:00
germondai a62019cd55 refactor(types): add raw response metadata 2026-07-24 09:17:42 +02:00
germondai 873eed005e ci(publish-baseline): tag every build with the rolling :baseline pointer 2026-07-23 00:45:29 +02:00
germondai 2d5ae6f30d ci(publish): strip v prefix from docker tags + gate nightly-<sha> to main 2026-07-23 00:45:16 +02:00
germondai 23fd733c16 docs(readme): document all MITM proxy env vars + host/maxTier defaults 2026-07-23 00:45:03 +02:00
germondai 90fc9b59dd chore(tiers): export isChallengeWall + SolveResult type from public API v1.1.0 2026-07-22 23:15:09 +02:00
germondai 2840af1d13 chore(release): bump all packages to v1.1.0 2026-07-22 23:13:17 +02:00
germondai 0ad5522812 docs(changelog): add v1.1.0 release notes (MITM forward-proxy mode) 2026-07-22 23:13:17 +02:00
germondai f427ebb5b1 docs(env): document MITM_PROXY_HOST default + loopback override 2026-07-22 23:13:16 +02:00
germondai 7107b2f224 feat(proxy): make host required + universal challenge wall detection 2026-07-22 23:13:16 +02:00
germondai 000b48d2e7 feat(tiers): extract isChallengeWall as universal challenge predicate 2026-07-22 23:08:05 +02:00
germondai b67af4c7e6 fix(ci): inspect uses fully-qualified tag from metadata-action JSON 2026-07-22 20:06:04 +02:00
germondai f633edb5a8 fix(ci): inspect pushed tag instead of re-deriving from github.sha 2026-07-22 20:02:28 +02:00
germondai 2b146e36bd chore(ci): run ci on PRs targeting dev 2026-07-22 19:51:00 +02:00
germondai 3b3b643904 fix(proxy): bind listener to loopback by default with MITM_PROXY_HOST override 2026-07-22 19:50:30 +02:00
germondai 331df98dca feat(proxy): rotate proxy pool in fetchRaw on CF challenge 2026-07-22 19:50:08 +02:00
GermondandGitHub b7decaf02e Merge pull request #29 from aziz66/feat/mitm-proxy-mode
feat(proxy): browser-backed MITM forward-proxy mode (fingerprint-bound Cloudflare, e.g. 1337x)
2026-07-22 16:38:41 +02:00
GermondandGitHub 28eda7ca57 Merge branch 'dev' into feat/mitm-proxy-mode 2026-07-22 16:38:18 +02:00
germondai 92aec1d869 ci: tag releases as vX.Y.Z and main pushes as nightly 2026-07-22 16:02:41 +02:00
germondai c98dd7a5cb fix(ci): baseline image now ships with each versioned release 2026-07-22 15:44:33 +02:00
germondai 6223a4e593 perf: release v1.0.1 - slimmer image, faster boot, firefox telemetry/dead-feature prefs v1.0.1 2026-07-21 18:31:26 +02:00
David Young 7dae357103 fix(browser): bound every await in restartEntry; reclaim stalled checkouts
`BrowserPool.restartEntry` awaited `context.close()`, `browser.close()` and the
Camoufox launch with no timeout on any of them. Camoufox hangs on close when a
content process is wedged — tiers/3.ts and tiers/4.ts already guard their
*temporary* contexts against exactly this with a 5s `Promise.race` — but the
persistent context and browser the pool owns had no such guard, and launches can
hang too.

When any one of those hangs, the entry is pinned at `restarting = true` forever.
From then on the health check hits its own `if (entry.restarting) return` guard,
so every 30s tick logs "browser N disconnected, restarting" and does nothing.
The pool silently loses that slot permanently: `restartCount` never increments,
so the restart counter sits frozen while the log implies furious activity. With
enough uptime every entry ends up in this state and the pool is inert.

Changes:

  * every await in `restartEntry`, `init()` and `shutdown()` is bounded. On
    timeout the entry is left unhealthy with `restarting` cleared, so the next
    health-check tick retries it from scratch instead of wedging.
  * `runHealthCheck` reclaims checkouts past their deadline. Previously busy
    entries were skipped entirely, so an entry whose request wedged was never
    examined again.
  * a per-checkout `lease`, returned on the handle and passed back to
    `release()`, so a request that outlives its checkout cannot free — or
    recycle, via `noteTemporaryContext` — a browser the pool has since handed to
    someone else.
  * `release()` hands its in-flight page closes to `restartEntry` rather than
    racing them, since closing a context underneath in-flight `page.close()`
    calls is one way to wedge the transport in the first place.
  * abandoned launches are counted and capped. A timeout can only stop *waiting*
    for a launch, not cancel it, so retrying without a cap could pile up hung
    Firefox processes; past the cap the entry stays down and `live` reflects it.

Timeouts are configurable (`closeTimeoutMs`, `launchTimeoutMs`, `stallAfterMs`,
`healthIntervalMs`) with the API exposing them as BROWSER_*_MS env vars.

Adds regression tests for the hung close, the hung launch, stall accounting,
budget-aware stall deadlines, disconnected-but-busy entries, and stale releases.
The hung-close and hung-launch tests both fail against the unpatched pool.
2026-07-21 14:47:53 +12:00
David Young 961579724a fix(api): gate /health on real pool capacity, not available + busy
`/health` returns 200 as soon as `pool` is non-null, which happens before
`await pool.init()` has warmed any browser — so a readiness probe on /health
passes before the process can solve anything.

The obvious fix, `available + busy > 0`, is also wrong, and fails in a much
worse way. A request that hangs mid-solve never reaches the orchestrator's
`finally`, so it never calls `release()` and its entry stays `busy` for the life
of the process. `busy` therefore counts dead entries as capacity, and /health
can report 200/"ok" indefinitely on a pool with zero usable browsers — the
failure is completely invisible to any external check.

Adds `stalled` and `live` to PoolStats:

  * an entry is `stalled` once its checkout outlives the caller's own budget
    (req.maxTimeout, threaded through acquire()) plus a grace period, so a slow
    but genuinely live request is never miscounted
  * `live` counts entries that can serve work now or are genuinely mid-request:
    idle-and-connected, plus busy-and-connected-and-not-stalled

/health now gates on `live > 0`. A fully utilised pool still reports ready, so
this does not flap under load, but a wedged one cannot report ready at all.

`isUsable()` also checks `browser.isConnected()` rather than trusting the
`healthy` flag, which is only refreshed on the 30s health-check tick and is
never refreshed at all for busy entries.
2026-07-21 14:47:53 +12:00
Erik Dasque 4ac9e74bab feat(tiers): add Akamai Bot Manager (behavioral / sec-cpt) challenge support
trawl returned some Akamai-fronted pages as 200 'success' with only the
~2KB sec-cpt behavioral interstitial as content, because tier detection
knew Cloudflare/Imperva but not Akamai.

- detect.ts: hasAkamaiChallenge() + 'akamai' ChallengeType (sec-if-cpt-container
  / behavioral-content markers, size-gated sensor fallback); wired into
  detectChallengeType/isBlocked/needsJs.
- akamaiWait.ts (new): Akamai analogue of challengeWait/impervaWait — drives
  human-like mouse motion, press-and-hold on the behavioral widget, waits for
  the sensor's location.reload() into real content.
- tiers 1-4: escalate the 200 interstitial (needs-js), invalidate a stale
  cached-session interstitial, dispatch the resolver, report akamai-persistent.

Additive; Cloudflare/Imperva paths untouched. Verified against Edmunds.
2026-07-19 00:17:17 -04:00
aziz66andClaude Fable 5 aa719dd1fa feat(proxy): browser-backed MITM forward-proxy mode
The FlareSolverr /v1 contract only returns cookies + user-agent. Clients like
Prowlarr take those and re-fetch the target with their own HTTP stack, which is
re-challenged on sites whose Cloudflare clearance is bound to the solving
browser's connection fingerprint (e.g. 1337x) — no cookie is portable to a
plain HTTP client, so those indexers can't be used at all.

Add an optional HTTP(S) forward proxy (MITM_PROXY_ENABLED). Point such a client's
proxy at it (per-indexer HTTP proxy in Prowlarr) and every request — search and
the .torrent/magnet grab — is transparently re-issued through the browser pool,
returning the RAW response bytes so binary downloads pass through intact.

- ca.ts: self-generated CA (persisted) + on-demand per-host leaf certs
- server.ts: per-host loopback-TLS termination (Bun's node:tls can't drive a
  handshake via emit("connection") or honor SNICallback, so one listening TLS
  server per host is the reliable path); raw-byte capture via page.goto response
  body, with the download-event path for binaries; scrape() fallback solves CF
- /proxy-ca.crt route to fetch the CA for the client's trust store
- New env: MITM_PROXY_{ENABLED,PORT,CA_DIR,MAX_TIER,DEBUG}

Off by default; localhost-only by design (a MITM proxy can impersonate any host
to a client that trusts its CA).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 01:13:45 +04:00
GermondandGitHub 21ef01ac37 docs(readme): updated sponsor section 2026-07-15 04:19:01 +02:00
GermondandGitHub 95a2d733de Merge pull request #28 from AxLabs/fix/minor-git-clone-change
docs: minor git clone README change
2026-07-13 22:40:58 +02:00
Guil. Sperb MachadoandGitHub c0e423df51 docs: minor git clone README change 2026-07-13 20:31:48 +00:00
GermondandGitHub 1c949f90d8 docs(readme): tighten features for github line width and unify sponsors 2026-07-13 20:17:05 +02:00
GermondandGitHub 2cfa262b8d docs(readme): refresh marketing copy and add sponsor section 2026-07-13 20:08:49 +02:00
germondai f8c90f0c49 docs: replace dragonflydb references with redis 8.8 and reformat markdown 2026-07-11 17:00:29 +02:00
germondai f746206de9 docs: update trawl docker image size in compare table 2026-07-11 16:37:25 +02:00
germondai 438326f584 docs: add Releases & versioning section to README
Explains the SemVer tag -> publish.yml -> Docker tag flow for issue #24,
and how to publish a specific past commit without waiting for main's tip.
v1.0.0
2026-07-10 21:12:39 +02:00
germondai 6f96285544 chore(release): bump to 1.0.0 and backfill versioned changelog history
Splits the single [Unreleased] CHANGELOG block into dated 0.1.0-1.0.0
sections matching the milestone commits being tagged for issue #24
(numeric release tags), and bumps every package.json to 1.0.0.
2026-07-10 21:11:04 +02:00
germondai 040c7352fb chore(deps): bump typescript to v7 and update workspace dependencies 2026-07-10 19:19:35 +02:00
germondai 9311cb38a4 fix(a11y): drop redundant role attribute and update biome lint config 2026-07-10 19:17:37 +02:00
germondai 1f1a45ee0b style: use shallowRef for primitive refs in web nav 2026-07-10 17:35:25 +02:00
germondai c25889493a style: apply biome formatting to hcaptcha audio test script 2026-07-10 16:13:53 +02:00
germondai 4c46f61b8d docs: update README and changelog for the Redis 8.8 revert 2026-07-10 16:11:24 +02:00