Add domain-firewall skill — CDP navigation security for browser agents#63
Add domain-firewall skill — CDP navigation security for browser agents#63
Conversation
|
Inspired by @shubh24 's sample script for Notion who raised this as a top-of-mind guardrail for security |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Missing await on sendCDP calls in event handler
- I confirmed the missing awaits in
installDomainFirewalland addedawaitto allpage.sendCDPcalls in theFetch.requestPausedhandler to prevent unhandled rejections and stuck paused requests.
- I confirmed the missing awaits in
Or push these changes by commenting:
@cursor push 36f6f7fbd8
Preview (36f6f7fbd8)
diff --git a/skills/domain-firewall/SKILL.md b/skills/domain-firewall/SKILL.md
--- a/skills/domain-firewall/SKILL.md
+++ b/skills/domain-firewall/SKILL.md
@@ -300,7 +300,7 @@
// Pass through non-document requests (images, CSS, JS, fonts, etc.)
const resourceType = params.resourceType || "";
if (resourceType !== "Document" && resourceType !== "") {
- page.sendCDP("Fetch.continueRequest", { requestId: params.requestId });
+ await page.sendCDP("Fetch.continueRequest", { requestId: params.requestId });
return;
}
@@ -310,7 +310,7 @@
url.startsWith("about:") ||
url.startsWith("data:")
) {
- page.sendCDP("Fetch.continueRequest", { requestId: params.requestId });
+ await page.sendCDP("Fetch.continueRequest", { requestId: params.requestId });
return;
}
@@ -318,7 +318,7 @@
try {
domain = normalizeDomain(new URL(url).hostname);
} catch {
- page.sendCDP("Fetch.continueRequest", { requestId: params.requestId });
+ await page.sendCDP("Fetch.continueRequest", { requestId: params.requestId });
return;
}
@@ -334,13 +334,13 @@
time: ts(), domain, url: url.substring(0, 80),
action: "ALLOWED", decidedBy,
});
- page.sendCDP("Fetch.continueRequest", { requestId: params.requestId });
+ await page.sendCDP("Fetch.continueRequest", { requestId: params.requestId });
} else {
auditLog?.push({
time: ts(), domain, url: url.substring(0, 80),
action: "BLOCKED", decidedBy,
});
- page.sendCDP("Fetch.failRequest", {
+ await page.sendCDP("Fetch.failRequest", {
requestId: params.requestId,
errorReason: "BlockedByClient",
});This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Case-sensitive www prefix strip before lowercasing
- Updated domain normalization to lowercase before stripping the www prefix in both the runtime script and SKILL example code.
Or push these changes by commenting:
@cursor push e179cf303d
Preview (e179cf303d)
diff --git a/skills/domain-firewall/SKILL.md b/skills/domain-firewall/SKILL.md
--- a/skills/domain-firewall/SKILL.md
+++ b/skills/domain-firewall/SKILL.md
@@ -180,7 +180,7 @@
```typescript
function normalizeDomain(hostname: string): string {
- return hostname.replace(/^www\./, "").toLowerCase();
+ return hostname.toLowerCase().replace(/^www\./, "");
}
function ts(): string {
diff --git a/skills/domain-firewall/scripts/domain-firewall.mjs b/skills/domain-firewall/scripts/domain-firewall.mjs
--- a/skills/domain-firewall/scripts/domain-firewall.mjs
+++ b/skills/domain-firewall/scripts/domain-firewall.mjs
@@ -95,7 +95,7 @@
// =============================================================================
function normalizeDomain(hostname) {
- return hostname.replace(/^www\./, "").toLowerCase();
+ return hostname.toLowerCase().replace(/^www\./, "");
}
function ts() {This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.
|
Potential messaging: How it works |
93df23b to
df68c27
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit df68c27. Configure here.
CLI-first skill that protects Browserbase and local Chrome sessions from prompt injection, malicious redirects, and data exfiltration by intercepting navigations at the Chrome DevTools Protocol level. Includes: - SKILL.md: setup, quick start, agent workflow, CLI reference, best practices, troubleshooting - REFERENCE.md: TypeScript API with composable policy system (allowlist, denylist, pattern, tld, interactive) - EXAMPLES.md: 8 real-world use cases (banking, CRM migration, procurement, e-commerce, staging isolation, etc.) - marketplace.json: plugin registration with security category Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
df68c27 to
4663e68
Compare
Verified live: with urlPattern: "*" a single wikipedia nav took 29 Fetch.requestPaused round-trips (1 Document + 28 sub-resources); the handler pattern-matched and immediately continued all 28. Filtering at the CDP layer drops them before they hit our WS, no behavior change (handler already policed Documents only). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>


Summary
Adds a domain-firewall skill that protects Browserbase (and local Chrome) sessions from unauthorized navigations at the Chrome DevTools Protocol level. Designed to prevent prompt injection attacks that trick AI agents into navigating to attacker-controlled URLs.
Two interfaces:
node domain-firewall.mjs --session-id <id> --allowlist "example.com"attaches to a live session and enforces policies transparently. Works with thebrowseCLI, Stagehand, or any CDP client.installDomainFirewall(page, { policies, defaultVerdict })for embedding directly in Stagehand projects with composable policies.What it does
Documentnavigations viaFetch.requestPausedCDP eventFetch.failRequest("BlockedByClient")— Chrome showsERR_BLOCKED_BY_CLIENTBuilt-in policies (TypeScript API)
allowlist(domains)denylist(domains)pattern(globs, verdict)tld(rules).org,.edu,.ru)interactive(handler)CLI script flags
Security demo results
Tested against a honeypot page containing hidden prompt injection that directs agents to an attacker URL:
--allowlist localhost)Files
Test plan
bb sessions debug--cdp-urlFetch.continueRequest)Fetch.failRequest)--json)🤖 Generated with Claude Code
Note
Medium Risk
Adds a new CDP WebSocket client that can block browser navigations at the protocol level; mistakes in interception/attachment logic could break browsing sessions or block legitimate navigations.
Overview
Adds a new
domain-firewallskill to the marketplace, providing a CLI that connects to a Browserbase session (viabb sessions debug) or a local Chrome CDP URL and enforces domain-based navigation controls.The new
domain-firewall.mjsinterceptsDocumentnavigations using CDPFetch.enable/Fetch.requestPaused, applies denylist → allowlist → default verdict decisions (including*.domainwildcard matching), and logs decisions in human-readable or JSON modes with graceful shutdown handling.Includes packaged dependencies (
ws) plus reference docs and example usage covering high-risk automation scenarios.Reviewed by Cursor Bugbot for commit 245e377. Bugbot is set up for automated code reviews on this repo. Configure here.