Summary
Following Dark Reading's coverage of the "PleaseFix" zero-click agent-hijacking vulnerability class in agentic browsers (Zenity Labs disclosure, 2026-03-03), I did a code-level risk assessment of this server's tool surface against that threat model.
Full report: https://claude.ai/code/artifact/92beb8ed-9b37-4713-b27b-729512b0b94e
The core pattern: an agent pulls untrusted content (in our case, rendered web pages) into its context without reliably separating "data" from "instruction." An attacker who plants adversarial text in a page can hijack the agent's tool access with zero clicks, using whatever tools the agent already has standing access to. This server is exactly that kind of component — it's the tool layer an AI assistant uses to drive a real Firefox session.
No exploitation was performed; this is static analysis against server.py as of the current HEAD.
Findings
High severity
-
Unsanitized content relay — browser_get_source (server.py:1182), browser_get_text (:1187), browser_get_attribute (:1202), devtools_console (:793), devtools_js_errors (:768), browser_get_storage (:1601) all return page-controlled strings verbatim into the agent's context — no truncation, no HTML/script stripping, no "untrusted" framing. This is the direct injection channel the PleaseFix researchers describe.
-
Unrestricted JS execution — browser_execute_js (server.py:1307-1320) runs agent-supplied JS via driver.execute_script() with no allowlist or sandbox. A hijacked agent can run fetch()/XMLHttpRequest to exfiltrate data to an attacker origin, limited only by the target page's own CORS/CSP.
-
No URL/scheme/host gating — _normalise_url() (server.py:125-130) explicitly passes file:, data:, and about: URLs through unmodified rather than blocking them, and there's no host blocklist. browser_navigate/browser_open will go anywhere requested — including file://, localhost, RFC1918 addresses, or the 169.254.169.254 cloud metadata endpoint.
-
Cookie/storage exfiltration chain — browser_get_cookies (server.py:1565) + browser_get_storage (:1601) read secrets (including HttpOnly cookies, readable via the WebDriver API) with no redaction. browser_execute_js or browser_navigate can immediately ship those values cross-origin. Nothing in the server links, warns on, or rate-limits this sequence.
Medium severity
- No confirmation or audit layer —
readOnlyHint/openWorldHint tool annotations are advisory metadata consumed only by the MCP client's approval UI. server.py itself has no elicitation/confirmation call, no rate limiting, and no action log.
Informational
- The Firefox-profile lockdown (
AGENTS.md:54, tests/test_server.py::TestBrowserOpenHasNoProfileParams) is a real, deliberately tested mitigation against this exact threat class — browser_open has no per-call profile override so a page can't get the agent to open the user's real logged-in profile. It just doesn't cover the four High findings above.
- Minor doc/test drift, not a security issue: 43 tools documented vs. 42 asserted in
tests/test_server.py:214-233 (devtools_performance missing from the expected set).
Suggested order of work
- Envelope page-derived tool output as untrusted content (cheapest, highest-leverage — addresses the root injection channel).
- Redact cookie/storage values by default, behind an explicit opt-in to reveal raw values.
- Add an opt-in scheme/host allowlist for
browser_navigate/browser_open (block file://, loopback, RFC1918, link-local/metadata by default).
- Document the
execute_js + navigate exfiltration chain in README/AGENTS.md, next to the existing profile-lockdown note.
- (Larger) Optional server-side confirmation hook (FastMCP elicitation) for the highest-risk tools.
References
🤖 Generated with Claude Code
Summary
Following Dark Reading's coverage of the "PleaseFix" zero-click agent-hijacking vulnerability class in agentic browsers (Zenity Labs disclosure, 2026-03-03), I did a code-level risk assessment of this server's tool surface against that threat model.
Full report: https://claude.ai/code/artifact/92beb8ed-9b37-4713-b27b-729512b0b94e
The core pattern: an agent pulls untrusted content (in our case, rendered web pages) into its context without reliably separating "data" from "instruction." An attacker who plants adversarial text in a page can hijack the agent's tool access with zero clicks, using whatever tools the agent already has standing access to. This server is exactly that kind of component — it's the tool layer an AI assistant uses to drive a real Firefox session.
No exploitation was performed; this is static analysis against
server.pyas of the current HEAD.Findings
High severity
Unsanitized content relay —
browser_get_source(server.py:1182),browser_get_text(:1187),browser_get_attribute(:1202),devtools_console(:793),devtools_js_errors(:768),browser_get_storage(:1601) all return page-controlled strings verbatim into the agent's context — no truncation, no HTML/script stripping, no "untrusted" framing. This is the direct injection channel the PleaseFix researchers describe.Unrestricted JS execution —
browser_execute_js(server.py:1307-1320) runs agent-supplied JS viadriver.execute_script()with no allowlist or sandbox. A hijacked agent can runfetch()/XMLHttpRequestto exfiltrate data to an attacker origin, limited only by the target page's own CORS/CSP.No URL/scheme/host gating —
_normalise_url()(server.py:125-130) explicitly passesfile:,data:, andabout:URLs through unmodified rather than blocking them, and there's no host blocklist.browser_navigate/browser_openwill go anywhere requested — includingfile://,localhost, RFC1918 addresses, or the169.254.169.254cloud metadata endpoint.Cookie/storage exfiltration chain —
browser_get_cookies(server.py:1565) +browser_get_storage(:1601) read secrets (including HttpOnly cookies, readable via the WebDriver API) with no redaction.browser_execute_jsorbrowser_navigatecan immediately ship those values cross-origin. Nothing in the server links, warns on, or rate-limits this sequence.Medium severity
readOnlyHint/openWorldHinttool annotations are advisory metadata consumed only by the MCP client's approval UI.server.pyitself has no elicitation/confirmation call, no rate limiting, and no action log.Informational
AGENTS.md:54,tests/test_server.py::TestBrowserOpenHasNoProfileParams) is a real, deliberately tested mitigation against this exact threat class —browser_openhas no per-call profile override so a page can't get the agent to open the user's real logged-in profile. It just doesn't cover the four High findings above.tests/test_server.py:214-233(devtools_performancemissing from the expected set).Suggested order of work
browser_navigate/browser_open(blockfile://, loopback, RFC1918, link-local/metadata by default).execute_js+navigateexfiltration chain in README/AGENTS.md, next to the existing profile-lockdown note.References
🤖 Generated with Claude Code