sendblue-browser — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited sendblue-browser (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.
Long-running stealth Chromium (patchright) exposed via a tiny REST API. One process, many named sessions, durable cookies, one-click purge, and a CDP url for Playwright/Puppeteer to attach. Passes typical Cloudflare Turnstile and similar low-friction checks; not for hostile scraping at scale.
Repo: https://github.com/sendblue-api/sendblue-browser-use
curl -s http://127.0.0.1:8787/healthIf you get a JSON { ok: true, ... } response, skip to "Drive a session". Otherwise:
# Local dev (Bun)
cd <path-to-sendblue-browser-use>
touch .env
if [ -z "${BROWSER_USE_API_KEY:-}" ] && grep -q '^BROWSER_USE_API_KEY=' .env; then
export BROWSER_USE_API_KEY="$(grep '^BROWSER_USE_API_KEY=' .env | tail -1 | cut -d= -f2-)"
fi
export BROWSER_USE_API_KEY="${BROWSER_USE_API_KEY:-$(openssl rand -hex 32)}"
if grep -q '^BROWSER_USE_API_KEY=' .env; then
sed -i.bak "s/^BROWSER_USE_API_KEY=.*/BROWSER_USE_API_KEY=$BROWSER_USE_API_KEY/" .env && rm -f .env.bak
else
printf '\nBROWSER_USE_API_KEY=%s\n' "$BROWSER_USE_API_KEY" >> .env
fi
bun src/index.ts &
# Docker
docker compose up -dWait ~3s, then re-check /health. For later shell commands, reload the token with source .env.
Every endpoint except /health requires a bearer token (BROWSER_USE_API_KEY env var):
Authorization: Bearer $BROWSER_USE_API_KEYTreat the token as local browser/network control: a token holder can run page scripts and navigate Chromium to private or localhost services reachable from the daemon host.
TOKEN="Authorization: Bearer $BROWSER_USE_API_KEY"
# 1. Create a session. persistent=false exposes a CDP url; persistent=true backs to an on-disk profile.
curl -s -X POST http://127.0.0.1:8787/sessions \
-H "$TOKEN" -H "Content-Type: application/json" \
-d '{"name":"qa","persistent":false,"viewport":{"width":1440,"height":900}}'
# 2. Navigate. http/https only; file:// and chrome:// are rejected. timeoutMs default 30s.
curl -s -X POST http://127.0.0.1:8787/sessions/qa/navigate \
-H "$TOKEN" -H "Content-Type: application/json" \
-d '{"url":"https://example.com/","waitUntil":"networkidle","timeoutMs":15000}'
# 3. Screenshot. ?fullPage=true and ?selector=CSS supported. Returns image/png.
# With NAV_SCREENSHOT_POLICY=headless (default), headed sessions skip automatic
# nav screenshots to avoid visible capture flicker. Call this explicitly for headed evidence.
curl -s "http://127.0.0.1:8787/sessions/qa/screenshot?fullPage=true" \
-H "$TOKEN" -o /tmp/shot.png
# 4. Eval JS in page. EXPRESSION FORM ONLY — wrap statements in an IIFE.
# Body cap 200 kB. 422 if the eval throws inside the page.
curl -s -X POST http://127.0.0.1:8787/sessions/qa/script \
-H "$TOKEN" -H "Content-Type: application/json" \
-d '{"code":"(() => ({ title: document.title, h1: document.querySelector(\"h1\")?.innerText }))()"}'
# 5. Cookies (Playwright shape: name+value + url OR domain+path)
curl -s -X POST http://127.0.0.1:8787/sessions/qa/cookies \
-H "$TOKEN" -H "Content-Type: application/json" \
-d '{"cookies":[{"name":"session","value":"abc","url":"https://example.com"}]}'
curl -s "http://127.0.0.1:8787/sessions/qa/cookies?url=https://example.com" -H "$TOKEN"
# 6. Console ring buffer (last N messages from page)
curl -s "http://127.0.0.1:8787/sessions/qa/console?limit=50" -H "$TOKEN"
# 7. Clear cookies + active-page storage but keep the session id
curl -s -X POST http://127.0.0.1:8787/sessions/qa/purge -H "$TOKEN"
# 8. Close + delete profile
curl -s -X DELETE http://127.0.0.1:8787/sessions/qa -H "$TOKEN"For multi-step interactions (forms, OTP, file upload, real mouse), drive via Playwright. Non-persistent sessions expose a CDP websocket URL; persistent ones do not (they have their own private browser instance).
const { cdpUrl, targetId } = await fetch("http://127.0.0.1:8787/sessions/qa/cdp-url", {
headers: { Authorization: `Bearer ${process.env.BROWSER_USE_API_KEY}` }
}).then(r => r.json());
const { chromium } = require("playwright");
const browser = await chromium.connectOverCDP(cdpUrl);
async function targetIdFor(page) {
const cdp = await page.context().newCDPSession(page);
try {
const info = await cdp.send("Target.getTargetInfo");
return info.targetInfo?.targetId;
} finally {
await cdp.detach().catch(() => {});
}
}
let page;
for (const context of browser.contexts()) {
for (const candidate of context.pages()) {
if (await targetIdFor(candidate).catch(() => undefined) === targetId) page = candidate;
}
}
if (!page) throw new Error(`CDP target ${targetId} not found`);
await page.locator("input[name=emailAddress]").pressSequentially("[email protected]", { delay: 50 });
await page.locator("input[name=password]").pressSequentially("hunter2!", { delay: 50 });
await page.locator("button[data-localization-key='formButtonPrimary']").first().click({ delay: 90 });Puppeteer works identically with puppeteer.connect({ browserWSEndpoint: cdpUrl }).
| Pick | When |
|---|---|
persistent: true (default) | Log in once, debug for a week. Cookies + localStorage + IndexedDB persist under ~/.sendblue-browser-use/profiles/<name>/. No CDP attach. |
persistent: false | Need Playwright/Puppeteer over CDP. State is in-memory only. Each session is a fresh BrowserContext on a shared Chromium process. |
Every non-2xx response: { "error": { "code": "...", "message": "..." } }. Common codes:
| Status | Code | Meaning |
|---|---|---|
| 400 | empty_body | request body required |
| 400 | malformed_json | body not valid JSON |
| 400 | invalid_body | failed Zod validation (see error.details) |
| 401 | unauthorized | missing/wrong bearer token |
| 404 | not_found | no such session |
| 409 | already_exists | session name collision |
| 409 | cdp_unavailable_for_persistent_session | persistent sessions have no shared CDP url |
| 422 | script_failed | eval threw inside the page or timed out |
| 500 | internal_error | unexpected — check server logs |
| 502 | navigate_failed screenshot_failed cookie_read_failed cookie_write_failed session_unreachable | Chromium-side failure |
Playwright errors are truncated to one line + 300 chars before being returned to clients; the full message stays in server logs.
See examples/:
examples/attach-from-playwright.ts — Playwright over CDPexamples/attach-from-puppeteer.ts — Puppeteer over CDPexamples/multi-agent-debug.ts — two agents driving two surfaces in parallelexamples/run-script.sh — curl-only walkthrough~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.