Open Security Teardown / 02

Browser agents need a hard boundary.

A local, read-only architecture review of browser-use: how page content reaches the model, how actions reach Chromium, and where a production deployment still needs deterministic policy.

Static AST + source review No target execution No LLM / paid API Local artifact only
Formal AxiomGate outcome
0/100
D — AT RISK. The bounded 300,000-byte preflight completed; 3 medium and 161 low findings reduce the raw score to -237, clamped to 0.
ASSESSABLE · LIMITATIONS DISCLOSED

Review snapshot

Commit
6e1977daa0f67c9de0bc0e16aaec8b5833eeb8e0
Python files
274 bounded · 0 syntax errors
Engine findings
408 total · 244 info · 161 low · 3 medium
Coverage note
273 generic parser limitations — that is, 273 of the 274 files matched no framework-specific adapter matched and were analysed by the generic heuristic adapter instead. All 274 files parsed, with 0 syntax errors; “uncovered” means unrecognised by a framework adapter, not unread, which is why 118 of them still carry findings. A generic-adapter result is weaker evidence than a framework-adapter one, a parser limitation is not a finding, and a file that was not covered by a framework adapter was not concluded to be safe.
Scan scope id
tree_sha 419ab9358e627eae — this is a scan-scope and tree-shape identifier, not a content hash and not a cryptographic digest: it is not collision-resistant, and AxiomGate's own verification script demonstrates a collision between differing trees of equal file sizes. The upstream git commit above is what binds the content.
Published artifact digest
SHA-256 of the published findings.json artifact: fc2a27944c3df9390e2968d5406af51f7f4ffd930cd6a9db3d8388ed01d975b2 — this identifies the published evidence file itself. It is a different kind of identifier from the upstream git commit (which binds the analysed source), from the scanner tree_sha above (a scan-scope identifier, not a content hash), from the demo fixture digest, and from the formal artifact hashes. Verify it with sha256sum findings.json.
Raw evidence
findings.json — every automated finding with its rule id, rule version, source location, confidence and explicit “cannot claim” statement, plus the exact analyzer configuration used. Automated static output only; the manual review section below is separate and is not analyzer output.
Review themes
5 architectural hardening gaps
Revalidated
2026-09-18 · analyzer checkpoint 21419c03
Publication
Local only
01 / Control plane

Where authority travels

The reviewed flow is capable by design. The missing piece is a single, final decision that distinguishes page data from agent instructions and intent from authority.

01Web page

DOM, titles, popups, links, redirects

02Agent context

Browser state, task, history, sensitive state

03LLM output

Model-selected action and parameters

04Action registry

Click, input, files, evaluate, MCP

05Browser / state

Chromium, cookies, profiles, egress

Policy gap: the flow has validation and watchdogs, but no universal deny-by-default capability broker at the final side-effect boundary.
02 / Manual source review

Five hardening priorities

Manual source review, read against the same frozen commit. The engine emitted none of these: they are architectural readings, not analyzer detections. None is an unauthenticated RCE claim; the risk depends on task trust, deployment mode, and authenticated browser state.

BU-01High

No universal capability decision at the action boundary

The model can select navigation, click, input, upload, file, page-JavaScript, and MCP actions. Domain metadata shapes the model schema, but the final invocation does not visibly carry a universal capability decision.

tools/service.py:1833-1850, 2178-2253
tools/registry/views.py:14-29
tools/registry/service.py:331-360, 539-544
BU-02High

Page text shares the instruction channel

DOM, tabs, titles, events, history, task state, and sensitive-data state are assembled into one user message. Replacement redaction helps, but uncertainty is not a terminal deny.

agent/prompts.py:252-334, 353-359, 410-428
agent/message_manager/service.py:556-600
BU-03High

Optional isolation and partial egress policy

Allowlists and IP blocking are opt-in. The watchdog allows data and blob schemes, and URL checks do not demonstrate request-level egress enforcement or DNS destination pinning.

browser/profile.py:625-639, 917-939
watchdogs/security_watchdog.py:194-220
BU-04High

Authenticated state has a wide blast radius

Storage-state export can return decrypted cookies from all domains and write them to a caller-selected path. Persistent profile reuse is powerful, but scope, retention, and encryption are not enforced here.

browser/session.py:1435-1476
browser/profile.py:557, 850-905
BU-05Medium / High

Fail-soft recovery is not universal fail-closed

Timeouts exist, yet broad step and action handlers can convert failures into model-visible results and continue. Security denial should be a typed terminal state, separate from operational retry.

agent/service.py:1035-1085, 1258-1307
tools/service.py:2189-2253
03 / Risk mapping

From source to control

Taxonomy is a lens for prioritization, not a certification or a legal conformity opinion.

ReferenceWhat the review showsDeterministic response
OWASP LLM01
Prompt Injection
External page material reaches the instruction-bearing model context.Typed untrusted-data envelope; raw DOM never grants an action.
OWASP LLM02
Sensitive Disclosure
Secret state and browser cookies have model and durable-storage paths.Opaque origin-scoped handles; fail closed on redaction uncertainty.
OWASP LLM06
Excessive Agency
Broad model-selected browser, file, JavaScript, and MCP surface.Final deny-by-default capability broker with budgets and approvals.
EU AI Act
Article 15
Robustness and cybersecurity depend on configuration and recovery semantics.Mandatory network policy, sandbox preservation, typed terminal denials.
04 / Hardening path

A small boundary with a large payoff

The recommended fixes preserve the useful automation plane while making authority explicit, testable, and bounded.

01

Sanitize the input

Strip executable and hidden markup, attach provenance and a content hash, and label page content as data only.

02

Broker every action

Authorize at final invocation against origin, capability, data scope, approval, and remaining budget.

03

Pin the destination

Require HTTPS origins, resolve and pin approved IPs, and enforce redirects and egress at the network boundary.

04

Minimize identity

Use ephemeral profiles and opaque, origin-scoped cookie handles instead of all-domain export.

05

Terminate on doubt

Make policy, redaction, sandbox, identity, and egress uncertainty terminal rather than model-retryable.

06

Declare extensions

Require risk and capability declarations for custom actions and dynamically discovered MCP tools.

External content may inform the agent. It must never grant authority.

Read the full evidence ledger and the concrete Python capability-broker proposal in the local teardown package.

Open the full teardown ↗
05 / Method

Read-only means read-only

The reviewed snapshot was acquired once, then analyzed offline. No target behavior was invoked.

What was verified

  • Shallow clone at a fixed commit, isolated under Codex scratch storage.
  • 274 Python files parsed locally with Python AST at the 300,000-byte bound; 0 syntax errors.
  • Automated static analysis issued 0/100, D — AT RISK, with 273 generic parser limitations disclosed.
  • BU-01 – BU-05 are manual source review of the same snapshot, reported separately from the engine findings.
  • AxiomGate linter returned zero findings, retained as supplementary evidence only.
  • Scratch clone was removed after extraction; only local report artifacts remain.

What was not done

  • No target imports, browser launches, subprocesses, LLM calls, or provider calls.
  • No exploit attempts, dynamic page interaction, or external API traffic during analysis.
  • No GitHub issue, advisory, social post, commit, push, or deployment.
  • No claim that a specific deployment is exploitable or legally non-compliant.