DEV Community

Marceli Pawliński
Marceli Pawliński

Posted on

A Browser Automation CLI Is a Security Boundary, Not Just a Convenience Tool

I like tools that make tedious tasks shorter. One of my projects, browser-harness, is a command-line wrapper around browser automation: open a page, extract text, take a screenshot, fill a form, or run JavaScript in a page. Building it exposed different parts of how browsers work.

But reviewing a browser tool through a security lens raises a less comfortable question: who gets to decide what the browser is allowed to do?

That matters even more when an AI agent is choosing commands. An automated workflow might accept the next URL or instruction without questioning it.

This article is a review of the design trade-offs visible in my public repository, not an announcement that I've fixed them all. The standalone project is kept for reference; its README points to system-automation for newer development.

The convenience is real

The CLI exposes commands such as open, text, screenshot, fill, cookies, and exec. Those operations look like small, separate utilities, but they share something important: each is acting with the authority of a browser process.

Screenshots can capture sensitive information; cookies can reveal sessions; forms can change remote state. Page JavaScript gives the caller access to the live document and browser APIs.

Those capabilities are useful. The problem is treating them as interchangeable without distinguishing observation from action.

Read-only is a useful distinction, but not a guarantee

For an agent, I would start by dividing browser operations into three groups:

  1. Observation: read a page title, extract visible text, inspect public metadata.
  2. Potentially sensitive observation: screenshots, cookies, authenticated page content.
  3. State-changing or privileged actions: form submissions, clicks that trigger transactions, downloads, arbitrary page JavaScript.

The categories aren't perfect: even a GET request may have side effects, and screenshots can reveal private information. But explicit categories are better than none.

A practical interface could label an action before execution and require an additional approval step for sensitive reads or writes. Logs could then record the action, target origin, category and approval.

A URL is input, not permission

One weakness to watch for in any browser automation interface is unrestricted navigation. If an agent receives a URL from an untrusted page and immediately opens it, the page has influenced where the agent goes next.

An allowlist can reduce that risk. At minimum, normalise and parse the destination before comparing it with the permitted origins:

function isAllowedDestination(input, allowedOrigins) {
  let url;
  try {
    url = new URL(input);
  } catch {
    return false;
  }

  return (url.protocol === 'https:' || url.protocol === 'http:') &&
    allowedOrigins.has(url.origin);
}
Enter fullscreen mode Exit fullscreen mode

This is only a starting point. Real implementations also need to check redirects, DNS and network destinations where relevant, and the difference between a public website and a local or private address. A valid URL is not necessarily a permitted destination.

exec changes the trust model

The exec command in my repository is a useful example. It accepts JavaScript to run in the page context. That's flexible for debugging and extracting information, but it also means the caller is no longer limited to a small set of predefined operations.

If the caller is an AI agent that has read instructions from a webpage, the risk is not simply that the agent might make a mistake. The webpage may contain instructions designed to redirect the agent's behaviour. That's a prompt-injection problem crossing into browser privileges.

For a safer agent-facing design, I would avoid offering unrestricted script execution as the default. Prefer narrowly scoped commands with defined inputs and outputs. If arbitrary execution is necessary, keep it behind a separate permission boundary, restrict the environment, and record exactly what was run. Cookie export needs similarly strict treatment.

Headless browser flags deserve attention too

The current public browser.js includes a launch configuration with --no-sandbox. This is common in constrained container examples, but it is not a security improvement. Removing the browser sandbox weakens an important layer of isolation.

A production design should prefer running with the sandbox enabled wherever the environment supports it. If a deployment genuinely requires a different configuration, that exception should be documented and constrained by other isolation measures. Convenience flags can outlive their original justification.

What I would test before trusting an agent with it

I would build a small test matrix rather than rely on one happy-path demo:

  • A normal page with a valid title and visible text.
  • A page that redirects to an origin outside the allowlist.
  • A page containing instructions that try to change the agent's task.
  • A form where submission must be explicitly authorised.
  • An authenticated page containing data that must not appear in logs.
  • A navigation timeout or browser crash, followed by cleanup.

Those are proposed tests, not results I'm claiming to have run. Each should have an expected decision and an observable reason for it. A blocked action is a successful outcome when blocking was the intended policy.

The lesson I keep returning to

Automation is often described in terms of what it can do. I think the more useful question is what it should refuse to do without additional context.

Building browser-harness helped me understand the mechanics of controlling a browser. Reviewing it this way makes the next design problem clearer: a good agent tool needs capabilities, but it also needs boundaries, predictable failures, and evidence of what happened.

The repository is public if you want to inspect the commands and make your own assessment: marceli1404/browser-harness. I'd be interested in how other developers separate safe inspection from privileged browser actions.

Top comments (0)