Skip to content

v3.4.0 (2026-06-13)

Choose a tag to compare

@github-actions github-actions released this 13 Jun 07:18
· 34 commits to main since this release

Added

  • redirect_first_party site option (default true) — by default a redirect's destination domains (and chain hops) are registered first-party so the landed site's own resources aren't captured as third-party. Set false to keep redirect targets third-party, so filterRegex/dig apply to them under thirdParty: true — e.g. capturing the end domain of an ad/cloak redirect chain (which Chrome reaches via the ERR_TOO_MANY_REDIRECTS curl-resolve recovery). The originally-scanned domain stays first-party either way.
  • ERR_TOO_MANY_REDIRECTS is recovered, not hard-failed — a redirect-cloaking chain (rotating throwaway domains) can exceed Chrome's ~20-hop ceiling. The scanner now recovers via two complementary paths: (1) it first waits briefly for the browser to ride through on its own — a JS/meta hop on a committed page resets Chrome's hop counter and often carries the page to the end site for free; (2) if the page parked on chrome-error:// instead and the site has curl: true, it resolves the chain endpoint with curl (which, unlike headless Chrome, isn't served the endless-loop variant) and navigates there directly — a short hop that lands on the real end site. Either way it captures the end site's ad/tracker requests; falls back to the captured chain requests when neither path lands. The curl step is opt-in via the existing curl site option (so curl: false/unset never shells out to curl) and is also skipped under a proxy/VPN (curl runs direct and would leak the real IP / resolve from the wrong network); the free ride-through always applies.
  • click_elements site option — after a page loads, click a list of CSS selectors in order (searched across the main frame and any iframe) (e.g. ["a[href*='/movie/']", ".play"] to click a movie link then a play button). Reaches content via organic navigation/gesture instead of a direct deep-load, which some sites JS-redirect away, and triggers click-only content like video players. Each selector is waitForSelector-ed (visible) up to click_wait before clicking, so JS-rendered targets like video players aren't missed by racing ahead of them. The request interceptor stays attached, so the post-click page's requests run through the same filterRegex/dig matching; a click that navigates is followed and later selectors query the resulting page. Honors realistic_click (genuine trusted gesture) and cursor_mode: "ghost" (Bezier travel to the element); missing elements are skipped and never fail the scan. Settle/nav wait per click via click_wait (default 5000ms, capped at half the per-URL timeout).
  • --dns now also pins Chrome's page-navigation resolver via DoH. Chrome ignores --dns for navigation and reads /etc/resolv.conf directly, so a broken or filtering system resolver could ERR_NAME_NOT_RESOLVED a domain the pre-check had already resolved. When the --dns servers map to a known public DoH provider — Google, Cloudflare, Quad9, OpenDNS, AdGuard, CleanBrowsing, DNS.SB, Mullvad (incl. malware/family/unfiltered variants) — Chrome is launched with secure-DNS automatic mode pointed at that provider, so page navigation resolves through the same resolver as the pre-check. automatic (not secure) keeps a system-DNS fallback if DoH is unreachable rather than failing the batch. Applied to direct connections only — skipped when a proxy (--proxy-server) or VPN is active, since the exit/tunnel does the resolution and local DoH would be redundant or resolve geo-split domains to the wrong region. Unmapped resolvers (custom/ISP, per-account providers like NextDNS, IPv6) fall back to system DNS with a warning naming the supported providers.
  • --doh-disable site/CLI option (doh_disable in .nwssconfig), default off — opt out of the Chrome-navigation DoH pinning entirely. Chrome then resolves page navigation via the system resolv.conf even when --dns maps to a known provider, while the pre-check and dig still honor --dns. For networks where DoH adds latency or is blocked, or when system-path resolution is specifically wanted.

Changed

  • A clamped delay is now logged (--debug) — when delay exceeds its ceiling (the default 2s cap, or timeout/2 under delay_uncapped: true) it was silently reduced, so delay: 48000 quietly running as 29000ms looked like the flag was ignored. A debug line now reports the clamp and which ceiling applied (raise timeout, or set delay_uncapped: true, to lift it). The per-URL budget already reserves the full configured delay; this only surfaces the post-load dwell clamp.
  • DNS pre-check is paced and more tolerant under concurrency — a concurrent scan fired up to max_concurrent simultaneous c-ares UDP queries at the pinned --dns servers; the burst (rough on WSL2's UDP-through-NAT path, and rate-limited by public resolvers) produced timeouts / EREFUSED that tripped the circuit breaker (resolver errors N/M — suspending DNS pre-check) and lost the dead-host-skip optimization. The pre-check timeout is raised 2s → 4s (a clean NXDOMAIN still returns fast, so the higher ceiling only costs time when the resolver is genuinely slow), and createRotatingResolver now caps in-flight queries with a counting semaphore (default 6) so the burst is paced and excess callers queue and drain quickly. The circuit breaker itself is unchanged — these reduce the error rate so it stops tripping on healthy resolvers.

Fixed

  • whois availability probe is now platform-aware — the fallback used which whois (Unix-only), which on native Windows would false-negative an installed whois.exe whose whois --version errors (e.g. Sysinternals whois). Uses where on Windows, which elsewhere. No change on Linux/macOS/WSL.