
Cloak Fetch
- 6 installs
- 48 repo stars
- Updated May 18, 2026
- agents365-ai/cloakfetch
cloak-fetch is a Claude Code skill that fetches bot-protected or WAF-blocked pages through a stealth Chromium (CloakBrowser) and returns clean markdown.
About
A Claude Code skill used as a fallback when a web page will not load because it is behind anti-bot or WAF protection. It routes the URL through CloakBrowser, a stealth Chromium with a real TLS fingerprint, then extracts clean markdown with trafilatura. A developer uses it when WebFetch or curl returns a 403, 429, or a bot-challenge page. It requires CloakBrowser and trafilatura installed locally.
- Fallback fetch for pages blocked by Cloudflare, DataDome, Akamai, and other WAFs
- Routes URLs through CloakBrowser (stealth Chromium) and extracts markdown via trafilatura
- Recognizes vendor-specific 403/429 bot-block signatures to trigger proactively
Cloak Fetch by the numbers
- 6 all-time installs (skills.sh)
- Ranked #1,691 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
cloak-fetch capabilities & compatibility
Free; requires CloakBrowser and trafilatura installed in the same Python environment.
- Capabilities
- web scraping · bot bypass · content extraction
- Use cases
- web scraping · web search
- Platforms
- macOS · Linux · Windows
- Pricing
- Free
What cloak-fetch says it does
or WAF screening, route the same URL through CloakBrowser (a real Chromium
They all eventually let a real browser through, and
npx skills add https://github.com/agents365-ai/cloakfetch --skill cloak-fetchAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 6 |
|---|---|
| repo stars | ★ 48 |
| Last updated | May 18, 2026 |
| Repository | agents365-ai/cloakfetch ↗ |
What it does
Fetch pages blocked by Cloudflare, DataDome, or other WAFs via a stealth browser and return markdown.
Who is it for?
Fetching content from sites behind anti-bot or WAF protection as a fallback to WebFetch/curl.
Skip if: 404s, 500s, or login-walled 401 pages, which CloakBrowser cannot fix.
When should I use this skill?
A recent fetch returned 403/429, a Just a moment / Access Denied page, or an empty body from bot detection.
What you get
Clean markdown extracted from a page that a plain HTTP fetcher could not load.
- Clean markdown of the fetched page
By the numbers
- Recognizes signatures for 9+ bot-protection vendors (Cloudflare, DataDome, Akamai, PerimeterX, Imperva, F5/Distil, Kasad
Files
cloak-fetch — bot-protection / WAF fetch fallback
When a normal HTTP fetcher fails because the target site uses bot protection or WAF screening, route the same URL through CloakBrowser (a real Chromium with anti-bot patches at the C++ level and a genuine TLS/JA3 fingerprint) and return clean markdown via trafilatura. The agent never has to tell the user "the page is unfetchable" — it can try this skill first.
The underlying engine doesn't care which vendor is blocking the request — Cloudflare, DataDome, Akamai, PerimeterX, Imperva, Distil, Kasada, AWS WAF, or a no-name WAF rule. They all eventually let a real browser through, and CloakBrowser is a real browser.
When to trigger
Use immediately after any of these symptoms from a recent fetch tool call:
| Symptom in tool output | Likely cause |
|---|---|
HTTP 403, 403 Forbidden, HTTP 429 | Bot protection or rate-limit block |
| Empty body / a few hundred bytes of nothing on a content-rich URL | Silent WAF / TLS-fingerprint reject |
| Page rendered as a near-empty shell with a JS challenge script | SPA-only page or anti-bot challenge |
Resource was not loaded, net::ERR_HTTP2_PROTOCOL_ERROR | Upstream blocked the fetch handshake |
Access denied, Blocked, You don't have permission | Generic WAF / bot detection |
Vendor-specific signatures
If the failure string roughly matches one of these, it's a CloakBrowser case — don't waste another round-trip on a plain HTTP client:
| Vendor | Telltale strings / headers |
|---|---|
| Cloudflare | Just a moment..., Enable JavaScript and cookies to continue, cf-ray: header, __cf_bm cookie, `Attention Required! \ |
| DataDome | blocked by DataDome, <title>blocked</title>, dd-cookie / datadome cookie, <head>...captcha-delivery.com, geo-block style 403 with empty body |
| Akamai Bot Manager | Access Denied with Reference #18. (long hex.epoch.hex) ID, <TITLE>Access Denied</TITLE> + Akamai <HTML><HEAD> boilerplate, Pragma: akamai-x-cache, akamai-bot-manager cookie |
| PerimeterX / HUMAN | Please verify you are a human, Access to this page has been denied because we believe you are using automation tools, _px* cookies (_pxhd, _px3), <title>Human Verification</title> |
| Imperva / Incapsula | Incapsula incident ID:, Request unsuccessful. Incapsula incident ID, _Incapsula_Resource, visid_incap_* cookie, X-Iinfo header |
| F5 / Distil | Pardon Our Interruption, As you were browsing something about your browser made us think you were a bot, distil_r_captcha, D_RID cookie |
| Kasada | <head> containing ips.js, x-kpsdk-cd / x-kpsdk-cr response headers, 429 with empty body + kasada reference |
| AWS WAF | Request blocked + <aws-waf-token>, awswaf cookie, body referencing aws-waf-token |
| Sucuri | Sucuri WebSite Firewall - Access Denied, <title>Sucuri WebSite Firewall - CloudProxy</title>, X-Sucuri-ID / X-Sucuri-Cache header, sucuri-cf-id cookie (very common on self-hosted WordPress) |
| reCAPTCHA / hCaptcha passive triggers | Interstitial that displays without user interaction — page replaced with g-recaptcha/h-captcha div and no real content. (See "When NOT to trigger" for interactive checkbox/slider cases.) |
| TLS / JA3 fingerprint reject | Connection drops, empty body, ERR_HTTP2_PROTOCOL_ERROR, SSL_ERROR_ZERO_RETURN on a site that loads fine in a normal browser |
Also trigger preemptively when the user asks to fetch a page on a domain known to live behind one of these stacks — publishers (science.org, nature.com, sciencedirect.com, jstor.org), news (nytimes.com, bloomberg.com, ft.com, wsj.com), retail (nike.com, adidas.com, sephora.com), travel (kayak.com, southwest.com), financial (most broker portals). Going straight to this skill avoids a guaranteed-to-fail plain-HTTP round trip.
When NOT to trigger
| Symptom | Why this skill won't help |
|---|---|
404 Not Found | Page genuinely doesn't exist |
500 Internal Server Error | Origin is broken, not blocking you |
401 Unauthorized / login wall | Credentials needed — CloakBrowser does not carry them |
| Plain network error (DNS, connection refused, no route to host) | Network unreachable, not a bot block |
| Interactive captcha — slider, image-grid (reCAPTCHA "select all crosswalks"), Cloudflare Turnstile checkbox that requires a click, hCaptcha challenge | Needs a human or a paid solver service. CloakBrowser passes passive fingerprint checks but does not solve human-interaction challenges. |
Geo-block where your IP's country is the actual reason (This content is not available in your region) | CloakBrowser uses the same IP — proxy/VPN is the fix, not a different browser |
| Site requires a session cookie, OAuth, or signed URL | No credential plumbing — fetch the auth artifact first via the proper channel |
| The normal fetcher already succeeded | No need to re-fetch |
In these cases, report the actual failure to the user instead of masking it.
How to invoke
One command — the wrapper picks the right Python, runs the headless browser, extracts the main content with trafilatura, and writes clean markdown to stdout:
<SKILL_DIR>/cloak_fetch.sh "<URL>"Where <SKILL_DIR> is wherever this skill is installed. Common locations:
- Claude Code:
~/.claude/skills/cloak-fetch - OpenClaw:
~/.openclaw/skills/cloak-fetch - Codex:
~/.codex/skills/cloak-fetch - Project-local:
.claude/skills/cloak-fetchorskills/cloak-fetch
A portable invocation that finds the skill across these locations:
for d in \
"$HOME/.claude/skills/cloak-fetch" \
"$HOME/.openclaw/skills/cloak-fetch" \
"$HOME/.codex/skills/cloak-fetch" \
".claude/skills/cloak-fetch" \
"skills/cloak-fetch"; do
if [ -x "$d/cloak_fetch.sh" ]; then
SKILL_DIR="$d"; break
fi
done
"$SKILL_DIR/cloak_fetch.sh" "https://www.science.org/content/page/information-authors-research-articles"The wrapper streams clean markdown on stdout. Save to a file with > out.md or pipe directly into further processing.
Behavior
- Headless by default — no browser window opens.
- Latency: ~20–40 s per call (browser launch + page render + content settle).
- Output: clean markdown via trafilatura. Page chrome, navigation, ads,
and cookie banners stripped. Headings, lists, links, and code blocks preserved. When trafilatura can't isolate a main content node, the wrapper falls back to emitting the rendered HTML so the agent still has something to read.
- Failure modes: exits non-zero with a message on stderr if no
cloakbrowser-enabled Python is found, the browser couldn't reach the URL, or CloakBrowser couldn't pass the challenge. Surface the failure honestly to the user — do not fabricate page content.
Configuration
| Env var | Purpose | Default |
|---|---|---|
CLOAKBROWSER_PYTHON | Path to the Python interpreter with cloakbrowser installed | ~/github/CloakBrowser/.venv/bin/python, falling back to python3 |
For more advanced tuning (headless toggle, content-selector wait list, settle timing), edit cloak_fetch.py directly — see comments in that file.
Example end-to-end
User: "Get the authors info from https://www.science.org/content/page/information-authors-research-articles"
1. Agent tries WebFetch — gets HTTP 403 Forbidden. 2. Agent recognises the 403 → invokes this skill:
~/.claude/skills/cloak-fetch/cloak_fetch.sh \
"https://www.science.org/content/page/information-authors-research-articles"3. ~25 s later, ~26 KB of clean markdown lands on stdout. 4. Agent answers the user from the markdown — no need to mention the underlying fetch took two attempts.
Related
- cloakFetch hook — same fallback wired up as a Claude Code
PostToolUse hook (fully automatic, no agent decision required). Use the hook on Claude Code; use this skill on agents that lack a hook system (Codex, OpenCode, OpenClaw, etc.).
#!/usr/bin/env python3
"""Fetch a Cloudflare-gated URL via CloakBrowser and print clean markdown to stdout.
Usage: cloak_fetch.py <url>
Stdout: clean markdown extracted from the rendered page via trafilatura.
Stderr: progress messages (safe to /dev/null).
Exit 0 on success, 1 on failure.
"""
import sys
import time
if len(sys.argv) != 2:
print("usage: cloak_fetch.py <url>", file=sys.stderr)
sys.exit(1)
url = sys.argv[1]
try:
from cloakbrowser import launch
except ImportError as e:
print(f"cloakbrowser import failed: {e}", file=sys.stderr)
sys.exit(1)
try:
import trafilatura
except ImportError as e:
print(f"trafilatura import failed: {e}", file=sys.stderr)
print("install via: pip install trafilatura", file=sys.stderr)
sys.exit(1)
try:
print(f"[cloak] launching headless browser", file=sys.stderr)
browser = launch(headless=True)
page = browser.new_page()
print(f"[cloak] navigating to {url}", file=sys.stderr)
page.goto(url, wait_until="networkidle", timeout=90000)
# Cloudflare interstitial flips the title to "Just a moment..." — poll for
# the real title up to 30s.
deadline = time.time() + 30
while time.time() < deadline:
title = page.title() or ""
if title and "Just a moment" not in title:
break
time.sleep(1)
# Wait briefly for an SPA content container. If none of these exist, the
# page is either a plain static doc or the site uses uncommon selectors —
# fall through to the time.sleep settle below in either case.
try:
page.wait_for_selector(
"main, article, .article__body, .core-container, .pb-page-body",
timeout=5000,
)
except Exception:
pass
time.sleep(2) # extra settle for late-loading JS chunks
print(f"[cloak] title: {page.title()}", file=sys.stderr)
html = page.evaluate("() => document.documentElement.outerHTML")
browser.close()
print(f"[cloak] rendered, {len(html)} bytes HTML", file=sys.stderr)
md = trafilatura.extract(
html,
url=url,
output_format="markdown",
include_links=True,
include_images=False,
include_comments=False,
favor_recall=True,
)
if not md:
print("[cloak] trafilatura found no main content; emitting raw HTML", file=sys.stderr)
sys.stdout.write(html)
else:
sys.stdout.write(md)
print(f"[cloak] done, {len(md)} bytes markdown", file=sys.stderr)
except Exception as e:
print(f"[cloak] failed: {e}", file=sys.stderr)
sys.exit(1)
#!/usr/bin/env bash
# cloak-fetch wrapper: locate a Python with cloakbrowser, fetch the URL
# headlessly via CloakBrowser, and emit clean markdown (trafilatura).
#
# Usage: cloak_fetch.sh <url>
# Stdout: clean markdown extracted from the page.
# Stderr: progress + error messages.
# Exit: 0 on success, non-zero on any failure.
set -uo pipefail
if [ $# -ne 1 ]; then
echo "usage: $(basename "$0") <url>" >&2
exit 2
fi
URL="$1"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Find a Python interpreter that can import cloakbrowser.
# Order: explicit env var → default install location → PATH python3.
PY=""
for candidate in \
"${CLOAKBROWSER_PYTHON:-}" \
"$HOME/github/CloakBrowser/.venv/bin/python" \
"$(command -v python3 2>/dev/null)"; do
if [ -n "$candidate" ] && [ -x "$candidate" ] && "$candidate" -c "import cloakbrowser" 2>/dev/null; then
PY="$candidate"
break
fi
done
if [ -z "$PY" ]; then
echo "cloak-fetch: no Python with 'cloakbrowser' importable." >&2
echo "Install CloakBrowser (https://github.com/CloakHQ/CloakBrowser) and set" >&2
echo "CLOAKBROWSER_PYTHON to its venv python." >&2
exit 1
fi
exec "$PY" "$SCRIPT_DIR/cloak_fetch.py" "$URL"
Related skills
FAQ
When should it not be used?
On 404 (page missing), 500 (broken origin), or 401 login walls, where CloakBrowser does not help.
What does it return?
Clean markdown extracted from the rendered page via trafilatura.