Skill v1.0.1
currentAutomated scan100/100+9 new
version: "1.0.1" name: unblock description: "Use when a fetched URL returns 4xx/blocked, hits a WAF or captcha, or when a JS-heavy SPA returns no usable body. Escalates through public APIs, Jina Reader, header-diverse curl, TLS impersonation, headless browsers, and free archive mirrors until validated content is returned. Zero API keys." effort: medium
Unblock — Adaptive Fetch Chain (Zero-Key)
No API keys. No signup. No config. Every phase runs on public endpoints,rate-limited free tiers, or self-hosted binaries that auto-install. Paidproviders exist only behind explicit--allow-paid.
Iron Law
First HTTP 200 is not success. Validate the body first.
A 200 with a Cloudflare challenge HTML, a login wall, or an empty SPA shell is failure. The chain stops only when content passes engine/validate.mjs.
Phases
| Phase | Probe | Cost | Auto without keys? | |
|---|---|---|---|---|
| 0a | Public APIs (Reddit, HN, arXiv, Bluesky, GitHub, NPM, oEmbed) | free | yes | |
| 0b | Jina Reader (r.jina.ai) | free at 20 RPM | yes | |
| 0c | yt-dlp metadata + subtitles (1800+ media sites) | free | yes (auto-install) | |
| 0d | Native body cleaners (Naver, Tistory, Brunch) | free | yes | |
| 1 | curl with rotating UA × headers × URL transforms | free | yes | |
| 2 | curl-impersonate (TLS spoof) + cookie warming + referrer chain | free | yes (auto-install) | |
| 3 | LightPanda headless | free | yes (auto-install) | |
| 4 | Playwright real Chrome | free | yes (auto-install) | |
| 5 | Free archives: Wayback + archive.today + AMP + RSS + OG rescue | free | yes | |
| 6 | Optional paid (Tavily / Exa / Firecrawl) | paid | needs --allow-paid |
The chain stops at the first probe whose body passes validate.mjs. Phase 6 never runs implicitly even with env keys present. Phase 0d and URL normalization detail: references/native-cleaners.md.
When to Use
- Eevee researcher hits 403/blocked during
/scc:research - Manual fetch of a hostile site
- WebFetch returns truncated/empty body for JS-heavy SPAs
Not for plain keyword search (use Jina Search via s.jina.ai) or bulk crawls.
Quick Start
node skills/unblock/engine/cli.mjs "<URL>"node skills/unblock/engine/cli.mjs "<URL>" --device mobile --jsonnode skills/unblock/engine/cli.mjs "<URL>" --selector "article" --tracenode skills/unblock/engine/cli.mjs "<URL>" --max-phase 4
Exit codes: 0 success, 1 exhausted, 2 invalid input.
Phase 0a — Public APIs (instant win)
Dispatched by URL host pattern.
| Pattern | Endpoint | Returns | |
|---|---|---|---|
reddit.com/r/*/comments/* | <url>.json | post + comments | |
news.ycombinator.com/item?id=N | hn.algolia.com/api/v1/items/N | full thread | |
arxiv.org/abs/* | export.arxiv.org/api/query | abstract + metadata | |
bsky.app/profile/*/post/* | public.api.bsky.app | post + replies | |
github.com/<o>/<r> and issues/PR | api.github.com | repo / issue + comments | |
npmjs.com/package/<p> | registry.npmjs.org/<p> | package + readme | |
pages exposing <link rel="alternate" type="application/json+oembed"> | discovered URL | oEmbed JSON |
Phase 5 — Free Archive Fallback
When Phases 1–4 all fail, the chain tries free mirrors: Wayback Machine, archive.today, AMP cache, RSS/Atom feed discovery, OG-tag rescue. Results carry via_archive: true; OG-rescue results also carry partial: true. See references/archive-fallbacks.md.
Phase 2 — Cookie Warming + Referrer Chain
curl-impersonate does not just spoof TLS. The probe runs a 2-hop sequence:
- GET
https://<host>/with empty referrer → capturesSet-Cookie - GET target URL with referrer = locale-matched search engine, cookies = step 1 jar
Defeats homepage-cookie gates and referrer-checking news sites without per-site rules.
Phase 1 — Header Diversity
Each retry rotates User-Agent and: Accept-Language matched to TLD, Sec-Ch-Ua / Sec-Ch-Ua-Mobile / Sec-Ch-Ua-Platform (Chrome 131 client hints), Sec-Fetch-Dest / Mode / Site, Accept-Encoding: gzip, deflate, br, zstd. WAFs gating on missing client hints fall here without escalation.
Hard Rules
R1 — No site-name hardcoding. engine/profiles.json and code under engine/** must not contain brand-specific selectors or domain matches except the Phase 0a public-API allowlist (which routes to public APIs, not selector scraping). Site-specific scraping hints flow only via --user-hint. Enforced by tests/skills/unblock/no-brand-hardcode.test.mjs.
R2 — Validate before declare. Every probe result passes through validate.mjs's 4-layer check (status, body length, challenge body, content-type) before being returned.
R3 — Auto-install on miss. Missing binary triggers a one-time install attempt. If install fails, skip to next phase. Never block the chain.
R4 — Trace is mandatory on failure. Failure mode always returns a JSON trace listing phase outcomes, error codes, elapsed times.
R5 — Read trace before retry. Identical retries are forbidden. Adjust --device, --selector, or --user-hint based on trace.
R6 — Paid is opt-in. Phase 6 never runs without explicit --allow-paid.
R7 — Single-user CLI, not a service. This skill fetches arbitrary user-supplied URLs without internal-IP filtering. Do not expose its CLI behind a network endpoint without adding host allowlisting upstream — it can otherwise be used to probe 169.254.169.254 and other private ranges (SSRF).
Eevee Researcher Integration
Eevee invokes Unblock automatically when WebFetch returns 4xx/5xx, an under-200-char body, a known challenge signature, or content-type mismatch. See references/eevee-flow.md.
Output
Returns JSON with ok, url, phase, probe, elapsed_ms, content, title, meta, trace. Trace records every probe outcome. On exhaustion, content may still be present with partial: true if Phase 5 OG-rescue caught anything.
Configuration
All optional. Defaults work zero-key.
| Variable | Effect | |
|---|---|---|
JINA_API_KEY | Lifts Jina Reader 20 RPM cap | |
TAVILY_API_KEY / EXA_API_KEY / FIRECRAWL_API_KEY | Phase 6 only with --allow-paid | |
UNBLOCK_TIMEOUT_MS | Per-probe timeout (default 15000) | |
UNBLOCK_MAX_PHASE | Cap chain (default 5) | |
UNBLOCK_CACHE_DIR | Cookie + binary cache (default ~/.cache/unblock) | |
UNBLOCK_SKIP_NETWORK_TESTS | Skip network-touching tests | |
UNBLOCK_ALLOW_PRIVATE_HOSTS | Set 1 to disable SSRF guard (private/loopback/metadata). |
Files
engine/cli.mjs argv, exit code, outputchain.mjs orchestrator, escalationdetect.mjs challenge / WAF signaturesvalidate.mjs 4-layer success validatortransforms.mjs URL variants + locale-aware headersintent.mjs URL vs keyword routercookie-jar.mjs in-memory Set-Cookie jarinstall.mjs auto-install binariesprofiles.json UAs, transforms, validator thresholdsprobes/{public-api,jina,yt-dlp,curl,impersonate,lightpanda,playwright,archive,paid-api}.mjsreferences/{waf-detection,tls-impersonation,archive-fallbacks,eevee-flow}.md