cloud-browser-automation — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited cloud-browser-automation (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Use cloud browser services (Browserbase, Browser Use Cloud) for web scraping when local tools fail due to Cloudflare protection, JavaScript rendering requirements, or anti-bot measures.
CRITICAL: On-demand only, close immediately
Always follow this pattern:
Why:
Escalation ladder (always try cheapest/fastest first):
1. Try Hermes web_fetch first
↓ (if fails or empty content)
2. Try direct curl with proper headers (Pattern 0 — works for most static sites)
↓ (if blocked or needs JS)
3. Try Hermes browser
↓ (if blocked by Cloudflare or sandbox issues)
4. Use cloud browser (Browserbase)Use cloud browser when:
DON'T use cloud browser when:
# Credentials (saved in memory)
API_KEY="bb_live_O6tVgdzl8B6WquBSj1XpuaC5hMc"
PROJECT_ID="a5008864-bbaa-4966-96e2-2272497b003d"
BASE_URL="https://www.browserbase.com/v1"
# Limits
MAX_CONCURRENT_SESSIONS=3
SESSION_TIMEOUT=5 # minutes (default)CRITICAL USER PREFERENCE:
"gini jadi lo pakai browserbase saat dibutuhkan aja, nah sesi langsung close saat beres"
Translation: Use on-demand only, close immediately after done.
Why:
Pattern:
# 1. Create session ONLY when needed
session = create_session()
# 2. Do the task
data = scrape_page(session)
# 3. Close IMMEDIATELY after done
close_session(session)DON'T:
curl -X POST "https://www.browserbase.com/v1/sessions" \
-H "X-BB-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"projectId": "'$PROJECT_ID'",
"browserSettings": {
"viewport": {"width": 1920, "height": 1080}
}
}'Response:
{
"id": "session-id-here",
"status": "RUNNING",
"connectUrl": "wss://connect-v2.usw2.browserbase.com/?signingKey=...",
"seleniumRemoteUrl": "http://connect-v2.usw2.browserbase.com/webdriver",
"expiresAt": "2026-05-03T13:00:00.000Z"
}Option A: Playwright (Python)
from playwright.async_api import async_playwright
async with async_playwright() as p:
browser = await p.chromium.connect_over_cdp(connect_url)
page = await browser.new_page()
await page.goto("https://example.com")
data = await page.evaluate("() => document.body.innerText")
await browser.close()Option B: Selenium (Python)
from selenium import webdriver
options = webdriver.ChromeOptions()
driver = webdriver.Remote(
command_executor=selenium_remote_url,
options=options
)
driver.get("https://example.com")
data = driver.find_element(By.TAG_NAME, "body").text
driver.quit()Option C: Manual (Fallback)
# Get debug URL
curl "https://www.browserbase.com/v1/sessions/$SESSION_ID/debug" \
-H "X-BB-API-Key: $API_KEY"
# Open debug URL in browser
# Navigate manually
# Screenshot/extract data manuallycurl -X POST "https://www.browserbase.com/v1/sessions/$SESSION_ID/stop" \
-H "X-BB-API-Key: $API_KEY"ALWAYS close, even if task fails:
try:
data = scrape_with_browserbase()
finally:
close_session() # Always close!Problem: Ubuntu 24.04 strict package management blocks pip install playwright/selenium.
Solutions:
# Run Playwright in container
docker run -it mcr.microsoft.com/playwright/python:v1.40.0-jammy \
python3 /tmp/scraper.pyPros: Isolated, no pip conflicts Cons: Need Docker (~500MB)
# Create session
SESSION_ID=$(curl -X POST ... | jq -r '.id')
# Get debug URL
DEBUG_URL=$(curl ... | jq -r '.debuggerFullscreenUrl')
# User opens URL manually
echo "Open: $DEBUG_URL"
echo "Navigate to target site"
echo "Screenshot and send to me"
# Close session
curl -X POST ".../sessions/$SESSION_ID/stop" ...# If automation blocked, delegate research
delegate_task(
goal="Research [topic] via web search",
toolsets=["web", "terminal"]
)Challenge: Site returns HTML but might have Cloudflare or anti-bot checks
Solution:
# Direct curl with proper headers (often bypasses basic protection)
curl -s -L -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" \
-H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8" \
-H "Accept-Language: en-US,en;q=0.5" \
-H "Connection: keep-alive" \
-H "Upgrade-Insecure-Requests: 1" \
"https://example.com" > /tmp/page.html
# If response is gzip-encoded, check file type first
file /tmp/page.html # Should show "HTML document" not "gzip compressed"
# Extract data with grep/sed/python
grep -oP '<title>\K[^<]+' /tmp/page.htmlWhen to use:
When to escalate:
Challenge: API returns Cloudflare challenge page, need full browser automation
Solution:
import requests
import asyncio
import websockets
import json
import time
TINYFISH_API_KEY = "your-api-key"
async def fetch_via_tinyfish(url):
# 1. Create browser session
headers = {"X-API-Key": TINYFISH_API_KEY, "Content-Type": "application/json"}
payload = {
"url": url,
"timeout_seconds": 300,
"browser_profile": "stealth",
"proxy_config": {"enabled": True, "type": "tetra", "country_code": "US"}
}
resp = requests.post("https://api.browser.tinyfish.ai", headers=headers, json=payload, timeout=60)
info = resp.json()
session_id = info["session_id"]
base_url = info["base_url"].rstrip("/")
# 2. Wait for Cloudflare bypass (CRITICAL: 30 attempts × 6 seconds)
page_ws_url = None
for attempt in range(30): # Not 20!
time.sleep(6) # Not 5!
try:
pages_resp = requests.get(f"{base_url}/pages", timeout=20)
pages = pages_resp.json()
if pages:
page = pages[0]
title = page.get("title", "")
url_check = page.get("url", "")
# Check BOTH title AND url (not just title!)
if "Just a moment" not in title and url in url_check:
page_ws_url = page.get("webSocketDebuggerUrl")
break
except:
pass
if not page_ws_url:
requests.delete(f"https://api.browser.tinyfish.ai/{session_id}", headers={"X-API-Key": TINYFISH_API_KEY})
raise Exception("Failed to bypass Cloudflare after 30 attempts")
# 3. Execute via CDP
async with websockets.connect(page_ws_url, max_size=20_000_000) as ws:
counter = 0
async def send(method, params=None):
nonlocal counter
counter += 1
msg_id = counter
await ws.send(json.dumps({"id": msg_id, "method": method, "params": params or {}}))
while True:
msg = json.loads(await ws.recv())
if msg.get("id") == msg_id:
return msg
await send("Runtime.enable")
result = await send("Runtime.evaluate", {
"expression": """
fetch('/api/endpoint')
.then(r => r.json())
.catch(e => ({error: String(e)}))
""",
"awaitPromise": True,
"returnByValue": True
})
data = result.get("result", {}).get("result", {}).get("value")
# 4. Close session immediately
requests.delete(f"https://api.browser.tinyfish.ai/{session_id}", headers={"X-API-Key": TINYFISH_API_KEY})
return data
# Usage
data = asyncio.run(fetch_via_tinyfish("https://example.com"))CRITICAL Cloudflare Bypass Settings:
When to use:
When to escalate to Browserbase:
Real-world example: RPOW2Swap API monitoring (see references/rpow2swap-cloudflare-bypass.md)
Pattern 0a: Analyzing Minified JS Bundles
If curl returns a SPA with minified JavaScript, you can often extract API patterns without Browserbase. See references/minified-js-analysis.md for grep-based techniques to find:
This works for most modern SPAs (React, Vue, Next.js) and costs zero resources vs Browserbase.
Challenge: Need to read public tweet content
Solution:
# Direct curl scraping (faster + free)
curl -s -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \
"https://x.com/USERNAME/status/TWEET_ID" > /tmp/tweet.html
# Extract JSON data
grep -o 'window.__INITIAL_STATE__={.*}' /tmp/tweet.html | \
sed 's/window.__INITIAL_STATE__=//' > /tmp/tweet.json
# Parse with jq or Python
jq '.entities.tweets.entities' /tmp/tweet.jsonWhen to use:
When to escalate to Browserbase:
See references/twitter-scraping-fallback.md for full technique.
Challenge: OpenSea blocks direct HTTP (403 Cloudflare)
Solution:
# 1. Create session
session = create_browserbase_session()
# 2. Navigate with Playwright
page = await connect_to_session(session)
await page.goto("https://opensea.io/collection/xyz")
await page.wait_for_selector('[data-testid="floor-price"]')
# 3. Extract data
floor_price = await page.text_content('[data-testid="floor-price"]')
volume = await page.text_content('[data-testid="volume"]')
# 4. Close immediately
await browser.close()
close_browserbase_session(session)Challenge: Site uses Cloudflare Turnstile
Solution:
# Browserbase automatically solves Cloudflare challenges
# Just navigate normally, no special handling needed
page = await connect_to_session(session)
await page.goto("https://cloudflare-protected-site.com")
# Browserbase handles Turnstile automatically
await page.wait_for_load_state("networkidle")
content = await page.content()Challenge: Content loaded via JavaScript, web_fetch returns empty
Solution:
# Wait for content to load
page = await connect_to_session(session)
await page.goto("https://spa-site.com")
await page.wait_for_selector('.content-loaded') # Wait for JS
data = await page.evaluate("() => window.appData")Error:
{
"statusCode": 429,
"error": "Too Many Requests",
"message": "You've exceeded your max concurrent sessions limit (limit 3, currently 3)"
}Solution:
# List running sessions
curl "https://www.browserbase.com/v1/sessions" \
-H "X-BB-API-Key: $API_KEY" \
| jq '.[] | select(.status=="RUNNING") | .id'
# Close old sessions
for sid in $(list_running_sessions); do
curl -X POST ".../sessions/$sid/stop" ...
doneError: Session expires after 5 minutes (default)
Solution:
keepAlive: true for longer tasksError: pip install playwright blocked by Ubuntu 24.04
Solution:
Browserbase pricing: Pay per minute of session time
Optimization strategies:
# ❌ BAD: Create at start
session = create_session()
# ... 2 minutes of prep work ...
scrape(session) # Wasted 2 minutes!
# ✅ GOOD: Create right before use
# ... prep work ...
session = create_session()
scrape(session) # No waste!
close_session(session) # ❌ BAD: Leave running
session = create_session()
scrape(session)
# ... session still running ...
# ✅ GOOD: Close ASAP
session = create_session()
scrape(session)
close_session(session) # Immediately! # ❌ BAD: One session per page
for url in urls:
session = create_session()
scrape(url, session)
close_session(session)
# ✅ GOOD: One session for all pages
session = create_session()
for url in urls:
scrape(url, session)
close_session(session) # Try cheap methods first
try:
data = web_fetch(url) # Free, fast
except CloudflareError:
try:
data = hermes_browser(url) # Free, slower
except SandboxError:
session = create_browserbase_session() # Paid, last resort
data = scrape_with_browserbase(url, session)
close_session(session)Problem: Sessions left running, hit rate limit, waste credits
Solution:
try/finally to ensure closureProblem: Session created at script start, expires before use
Solution:
Problem: Hit 429 error, script fails
Solution:
Problem: Use Browserbase for static sites, waste money
Solution:
Problem: Assume Playwright/Selenium available, script fails on VPS
Solution:
After using Browserbase:
# Verify session closed
curl "https://www.browserbase.com/v1/sessions" \
-H "X-BB-API-Key: $API_KEY" \
| jq '.[] | select(.status=="RUNNING")'
# Should return empty or sessions you intentionally left running
# Check data extracted
cat /tmp/scraped_data.json
jq '.' /tmp/scraped_data.json # Validate JSON
# Verify files cleaned
ls /tmp/browserbase* /tmp/scrape*
# Should be empty or cleaned upreferences/tinyfish-integration.md (REST API, not MCP)~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.