The Localhost Agent Was a Browser Attack Surface

The Localhost Agent Was a Browser Attack Surface

A coding agent can be bound to 127.0.0.1 and still be part of a remote attack surface.

That is the useful lesson from GHSA-632h-h47v-g4x4, a high-severity remote code execution vulnerability in OpenCode. Datadog Security Labs disclosed the bug on September 24, 2026. OpenCode 1.18.22 contains the fix.

The vulnerable component was not the model. It was the local control plane around the model, specifically an upgrade endpoint exposed by the web interface.

What changed

The advisory affects OpenCode versions from 1.14.30 through 1.18.21 under the conditions described by the maintainers. The vulnerable endpoint was /global/upgrade.

OpenCode’s server accepted a request that was supposed to select a release version. In the affected code path, an attacker-controlled value could instead be treated as an installation target. For npm-based installations, that target could resolve to an attacker-controlled package archive, whose lifecycle scripts then ran during installation.

The result was code execution on the machine running the agent server.

This is not a claim that every OpenCode installation was remotely exploitable. The affected installation methods, server exposure and browser interaction matter. The security advisory and Datadog’s report describe those prerequisites. The important point is narrower: a local HTTP API that mutates the agent installation was treated as if locality implied trust.

Localhost is not an authorization boundary

The attack did not require an attacker to connect directly to a public OpenCode port.

Datadog showed that a malicious webpage could use a top-level form submission to send a cross-origin POST to the local OpenCode server. Browser protections such as CORS did not stop this navigation pattern, and the researchers note that newer Local Network Access prompts do not currently block top-level navigations by design.

That turns a familiar security assumption inside out:

"It listens on localhost"
          is not the same as
"Only the intended operator can call it"

A browser is a deputy with access to the user’s loopback services. If the service accepts a state-changing request without its own authentication and origin checks, any webpage the user visits may become part of the request path.

The browser is not the root cause. It is the transport that exposes the missing trust decision.

The upgrade endpoint crossed two trust boundaries

The endpoint sat at the intersection of two dangerous capabilities.

First, it changed the software that would run the agent. An upgrade operation is not a normal read-only API call. It is a package installation and therefore a supply-chain decision.

Second, the endpoint accepted input from a network request and passed that input into a package-manager operation. Datadog describes the underlying flaw as content-type confusion combined with code injection in the upgrade path.

Those boundaries should have been explicit in the design:

BoundarySafer assumptionFailure in the affected design
Loopback HTTPRequests still need an authenticated callerLocality was treated as sufficient trust
Browser cross-origin trafficState changes require origin and CSRF defensesA top-level navigation could submit the request
Upgrade targetOnly signed, expected releases are installableAn attacker-controlled archive could become the target
Package lifecycleInstallation scripts are executable codeThe upgrade path reached a package install with attacker input

The mistake was compositional. Each individual shortcut looked convenient. Together they made a browser visit a code execution trigger.

The model was never in the loop

This incident is a useful correction to the way agent security is usually discussed.

There is no prompt injection in the exploit path described by Datadog. No model needs to be persuaded to ignore its system instructions. No autonomous plan needs to drift. The attacker targets the infrastructure that starts and upgrades the agent.

That makes the control-loop lesson uncomfortable. A system can have a well-behaved model and still expose a dangerous execution boundary. The model’s refusal behavior is irrelevant if an HTTP handler can install code before the model receives its next task.

The agent is not a single process. It is a model, a tool runner, a server, a browser integration, a package manager, credentials and a host operating system. The security boundary includes every component that can change what the next process executes.

What operators should change

Patch first. If OpenCode is installed through npm, pnpm or Bun and the installation is in the affected range, update to 1.18.22 or later as directed by the GitHub advisory. Do not infer safety from the service binding to loopback.

Authenticate state-changing local APIs. A password option is not a substitute for authorization design, but an unauthenticated upgrade endpoint should not exist on a service that can execute package lifecycle code.

Treat browser reachability as network reachability. Audit every local agent service for cross-origin state changes, including form submissions and navigations that do not use JavaScript fetch().

Separate upgrade from execution. An agent’s runtime should not be able to turn an arbitrary request into a package installation. Use a separate updater with a narrow, signed-release policy, or require an explicit operator action outside the agent server.

Reduce package-install authority. Run the agent with a filesystem and process identity that cannot silently replace its own executable environment. If an update must happen, make the decision visible, authenticated and auditable.

Test the path from a hostile browser. The relevant test is not only whether localhost rejects a direct unauthenticated request. It is whether an untrusted webpage can cause a state-changing request through the browser’s navigation mechanisms.

Local is a deployment detail, not a trust model

The OpenCode advisory is a vulnerability in one product and one endpoint. The architectural lesson is broader.

Local agent servers increasingly expose tools, sessions, model providers and lifecycle operations over HTTP. Developers call these services local because they run on their own machines. Attackers care about whether an untrusted page, extension, process or downloaded project can make them perform a privileged action.

The right question is not where the socket binds. It is which component is authorized to ask the service to change executable state, and what evidence the service checks before it obeys.

A localhost agent is still a networked system. If its control plane can install code, its trust boundary must be designed like one.

Sources:

Keep reading