Skip to content

browser-use/browsercode - #163

Open
sithu209 wants to merge 13 commits into
browser-use:lean-browser-promptfrom
sithu209:main
Open

sithu209 wants to merge 13 commits into
browser-use:lean-browser-promptfrom
sithu209:main

Conversation

@sithu209

@sithu209 sithu209 commented Sep 21, 2026

Copy link
Copy Markdown

Issue for this PR

Closes #

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Please provide a description of the issue, the changes you made to fix it, and why they work. It is expected that you understand why your changes work and if you do not understand why at least say as much so a maintainer knows how much to value the PR.

If you paste a large clearly AI generated description here your PR may be IGNORED or CLOSED!

How did you verify your code works?

Screenshots / recordings

If this is a UI change, please include a screenshot or recording.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

If you do not follow this template your PR will be automatically rejected.

  • It is not necessary to follow all the specifications mentioned above; we will use it exactly as it is.

Alezander9 and others added 13 commits August 22, 2026 15:14
A provider stream that opens, delivers a first chunk, then dies without
sending a finish chunk is reported by the AI SDK as finishReason "other"
with null usage. "other" is not in the FinishReason literal set, so
session/llm/ai-sdk.ts maps it to "unknown".

The two loop guards then disagreed. The error check at prompt.ts:1295
excludes "unknown", so no error was recorded. The loop-exit check did
not, so the loop broke. The turn exited cleanly mid-task with no error,
no timeout and no errored span, and the run was indistinguishable from a
model that gave up.

Measured on the Odysseys benchmark: 0.054% of LLM calls, ~5.3% of tasks
(11/200 and 12/200 on two full runs), and 100% of them terminal.

"unknown" is the fallback in every mapper (openai-chat, openai-responses,
anthropic-messages, gemini, bedrock-converse) and never denotes a normal
completion, so exclude it from the loop-exit check as well and let the
turn resample. The dead turn contributes no model messages, so the repeat
request is identical to the one that was dropped. A warning log keeps the
event visible when the resample succeeds.
The default endpoint is a general-purpose URL fetcher, so BROWSER_USE_API_KEY
is not bounded by whatever network allowlist the process runs under: anything
holding the key can ask the fetcher to retrieve an arbitrary host, and a
prompt-injected agent can put the key in that URL and read it back out of the
attacker's logs. An egress allowlist does not help, because reaching the
fetcher is exactly what it permits.

BCODE_FETCH_USE_ENDPOINT lets the caller interpose. A sandboxed agent can be
given a mediating proxy and a throwaway credential while the real key stays in
the parent process, which is the arrangement the RL harness in
benchmark-x-laminar needs. Unset, behaviour is unchanged.

The test runs a real server on a loopback port and asserts both that the
override is used and that the target url arrives in the body, since a proxy
has nothing to forward otherwise. Confirmed it fails against the unmodified
source rather than passing vacuously.
…ample

fix(opencode): resample turns with an unmapped finish reason
The variable names the host that receives X-Browser-Use-API-Key, so a bad
value leaks a credential rather than merely failing, and each rejection here
is an operator mistake catchable at startup.

Set-but-empty was the worst of them: `||` sent it back to the default, which
is the direct fetcher -- the exact path someone setting this variable is
trying to leave. A typo'd or unexpanded value silently restored the behaviour
the override exists to remove, which is the failure you least want to be
quiet. `??` distinguishes unset (use the default, the ordinary case) from set
and empty (a mistake).

Cleartext is rejected outside loopback, since a mediating proxy on the same
host is the normal local arrangement and both of our own callers satisfy this
already: sandboxes get an https tunnel, local runs get 127.0.0.1.

Validation is synchronous and throws rather than failing the Effect, because
an error channel on this layer would propagate into ToolRegistry through
registry.ts:430, and a startup misconfiguration is not a recoverable
condition.

Note for the review suggestion this came from: its loopback test used "::1",
but URL reports the IPv6 literal with brackets, so an IPv6 endpoint would
have been rejected. The test covers that case and fails against the
unbracketed form.
…oint

Three gaps in the validation added by the previous commit.

Loopback was three literal hostnames, but all of 127.0.0.0/8 is loopback and
a trailing dot is the same name in rooted form, so a proxy on 127.0.0.2 was
refused with a message claiming it was not loopback. The pattern is anchored
and numeric so a DNS name like 127.example.com is not mistaken for the subnet.

The scheme check ran only against https, so ftp://localhost passed startup on
the loopback exemption and would have failed at the first webfetch instead --
the deferred failure this validation exists to pull forward. http or https is
now required before the exemption is considered.

Both messages echoed the raw value, which can carry userinfo or a token in its
query, so a typo wrote a credential into stderr: the same log leak the override
exists to close. The cleartext message names url.origin, which drops userinfo,
path and query, and the parse failure names only the variable, since an
operator can read back their own environment. Tested against a value carrying
both a password and a query token.

Not adopted: the report also expected a proxy bound on 0.0.0.0 to be accepted.
That is a bind address, not a connect target, so refusing it as non-loopback
is correct.

All four cases fail against the previous implementation and pass against this
one. 13 pass / 1 skip; typecheck 17/17.
feat(bcode-browser): allow overriding the fetch-use endpoint
…(ENG-5625)

Clears 23 of the 24 open Dependabot alerts (17 medium, 7 low):

- dompurify 3.3.1 (session-ui) / 3.4.11 (ui, catalog) -> 3.4.13: closes 19
  of 20 dompurify alerts. CVE-2026-65901 (low) has no fixed release and
  needs a Vanta exception instead.
- astro 6.4.8 -> 7.1.0 (CVE-2026-59727, CVE-2026-59729, CVE-2026-73422;
  the last one requires >= 7.1.0). Along for the major bump:
  @astrojs/starlight 0.40.0 -> 0.41.7, @astrojs/solid-js 6.0.1 -> 7.0.2,
  and @astrojs/cloudflare 13.7.0 -> 14.1.7 (14.2.0 imports an astro
  internal that only exists from 7.2.0 despite its ^7.0.0 peer range).
  The astro/starlight overrides pin and the toolbeam-docs-theme peer
  patch are refreshed the same way as the astro 6 migration (PR browser-use#109) so
  the unmaintained theme keeps deduping onto the site's versions.
- @babel/core 7.28.4 -> 7.29.6 (CVE-2026-49356).

Verified: full astro build of packages/web (all locales + Pagefind),
typecheck + tests for ui and session-ui, root turbo typecheck.
…ange

Review feedback on browser-use#158: @astrojs/solid-js@7.0.2 peer-requires
solid-js ^1.9.13 while the catalog pinned 1.9.10, leaving the docs
site's Solid island on an unsupported peer combination.

- Catalog solid-js 1.9.10 -> 1.9.15.
- Drop patches/solid-js@1.9.10.patch: it cherry-picked solidjs/solid#2046,
  which ships upstream in 1.9.14+.
- Add a root solid-js override: @opentui/solid, @opentui/keymap, and
  @solidjs/start pin exactly 1.9.10, and without the override they each
  grew a nested solid-js copy — solid must stay a singleton per app.
  They previously ran against the deduped, patched 1.9.10, which is
  behaviorally 1.9.15's runComputation fix anyway.

Verified: single solid-js@1.9.15 in bun.lock, no peer warnings on
install, full astro build of packages/web, typecheck for tui/ui/
session-ui + root turbo (17/17), ui and session-ui tests.
astro@7.1.0 optionally peers @astrojs/markdown-remark at exactly 7.2.1;
packages/web pinned 7.2.0. Starlight's ^7.2.0 peer is satisfied either
way. Verified with a full astro build.
…ix-dependabot-vulnerabilities-in-browser-usebrowsercode-24

security: fix Dependabot alerts in dompurify, astro, and @babel/core
fix(browser): provision cloud browsers through V4

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 issues found across 15 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/opencode/src/session/prompt.ts">

<violation number="1" location="packages/opencode/src/session/prompt.ts:1126">
P1: When the provider repeatedly emits an unsupported or missing finish reason, this branch keeps `runLoop` alive indefinitely. Bound unknown resamples and surface a terminal error after the retry budget.</violation>
</file>

<file name="package.json">

<violation number="1" location="package.json:92">
P2: The solid-js bump from 1.9.10 to 1.9.15 drops the previous solid-js@1.9.10.patch without carrying its fix forward. If that patch corrected a bug that 1.9.15 does not include, the fix is silently lost in whichever package relied on it (the TUI, console, and app all consume solid-js). Confirm the patch's fix landed upstream in 1.9.15, or port the patch to 1.9.15, before merging.</violation>
</file>

<file name="packages/bcode-browser/src/fetch-use.ts">

<violation number="1" location="packages/bcode-browser/src/fetch-use.ts:35">
P2: `resolveEndpoint()` throws when `BCODE_FETCH_USE_ENDPOINT` is invalid, and this runs at layer-build time unconditionally — even when the feature is disabled. The layer is composed into the tool registry unconditionally (`packages/opencode/src/tool/registry.ts`: `layer: layer.pipe(Layer.provide(FetchUse.layer))`), and the webfetch tool yields `FetchUse.Service` while defining itself (`webfetch.ts:31`), so any tool-registry build runs the validation. Before this PR a bad value was impossible; now a stale or unrelated `BCODE_FETCH_USE_ENDPOINT` hard-fails tool-layer construction for a user who may never use webfetch and holds no `BROWSER_USE_API_KEY` (in which case there is no key to leak, so fail-fast protects nothing here). Gate the validation on the feature actually being usable so the disabled case stays quiet, or confirm the wider blast radius is intended.</violation>

<violation number="2" location="packages/bcode-browser/src/fetch-use.ts:40">
P1: When the configured endpoint returns a redirect, `FetchHttpClient` follows it and forwards `X-Browser-Use-API-Key` to the new origin. A local mediator or HTTPS proxy can therefore redirect the request to an external host and leak the key; reject redirects or follow them manually while removing the API-key header on cross-origin hops.</violation>

<violation number="3" location="packages/bcode-browser/src/fetch-use.ts:71">
P3: `BCODE_FETCH_USE_ENDPOINT` is new behavior that changes where the webfetch API key is sent (and that many values are rejected at startup), but it is documented nowhere in-tree. The README (`packages/bcode-browser/README.md`) and the opencode config docs (`packages/core/src/v1/config/config.ts:187`) describe only `BROWSER_USE_API_KEY`. Add a line to the README's contents/env section so operators know the variable exists and what values are accepted (https anywhere, http only to loopback).</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

if (
lastAssistant?.finish &&
!["tool-calls"].includes(lastAssistant.finish) &&
!["tool-calls", "unknown"].includes(lastAssistant.finish) &&

@cubic-dev-ai cubic-dev-ai Bot Sep 21, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When the provider repeatedly emits an unsupported or missing finish reason, this branch keeps runLoop alive indefinitely. Bound unknown resamples and surface a terminal error after the retry budget.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/src/session/prompt.ts, line 1126:

<comment>When the provider repeatedly emits an unsupported or missing finish reason, this branch keeps `runLoop` alive indefinitely. Bound unknown resamples and surface a terminal error after the retry budget.</comment>

<file context>
@@ -1108,9 +1108,22 @@ const layer = Layer.effect(
           if (
             lastAssistant?.finish &&
-            !["tool-calls"].includes(lastAssistant.finish) &&
+            !["tool-calls", "unknown"].includes(lastAssistant.finish) &&
             !hasToolCalls &&
             lastAssistant.parentID === lastUser.id
</file context>
Fix with cubic

fetch: (url, { timeoutMs }) =>
Effect.gen(function* () {
const request = yield* HttpClientRequest.post(ENDPOINT).pipe(
const request = yield* HttpClientRequest.post(endpoint).pipe(

@cubic-dev-ai cubic-dev-ai Bot Sep 21, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When the configured endpoint returns a redirect, FetchHttpClient follows it and forwards X-Browser-Use-API-Key to the new origin. A local mediator or HTTPS proxy can therefore redirect the request to an external host and leak the key; reject redirects or follow them manually while removing the API-key header on cross-origin hops.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/bcode-browser/src/fetch-use.ts, line 40:

<comment>When the configured endpoint returns a redirect, `FetchHttpClient` follows it and forwards `X-Browser-Use-API-Key` to the new origin. A local mediator or HTTPS proxy can therefore redirect the request to an external host and leak the key; reject redirects or follow them manually while removing the API-key header on cross-origin hops.</comment>

<file context>
@@ -32,11 +32,12 @@ export const layer = Layer.effect(
       fetch: (url, { timeoutMs }) =>
         Effect.gen(function* () {
-          const request = yield* HttpClientRequest.post(ENDPOINT).pipe(
+          const request = yield* HttpClientRequest.post(endpoint).pipe(
             HttpClientRequest.setHeaders({ "Content-Type": "application/json", "X-Browser-Use-API-Key": apiKey }),
             HttpClientRequest.bodyJson({ url, timeout_ms: timeoutMs }),
</file context>
Fix with cubic

Comment thread package.json
"@sentry/solid": "10.36.0",
"@sentry/vite-plugin": "4.6.0",
"solid-js": "1.9.10",
"solid-js": "1.9.15",

@cubic-dev-ai cubic-dev-ai Bot Sep 21, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The solid-js bump from 1.9.10 to 1.9.15 drops the previous solid-js@1.9.10.patch without carrying its fix forward. If that patch corrected a bug that 1.9.15 does not include, the fix is silently lost in whichever package relied on it (the TUI, console, and app all consume solid-js). Confirm the patch's fix landed upstream in 1.9.15, or port the patch to 1.9.15, before merging.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At package.json, line 92:

<comment>The solid-js bump from 1.9.10 to 1.9.15 drops the previous solid-js@1.9.10.patch without carrying its fix forward. If that patch corrected a bug that 1.9.15 does not include, the fix is silently lost in whichever package relied on it (the TUI, console, and app all consume solid-js). Confirm the patch's fix landed upstream in 1.9.15, or port the patch to 1.9.15, before merging.</comment>

<file context>
@@ -89,7 +89,7 @@
       "@sentry/solid": "10.36.0",
       "@sentry/vite-plugin": "4.6.0",
-      "solid-js": "1.9.10",
+      "solid-js": "1.9.15",
       "vite-plugin-solid": "2.11.10",
       "@lydell/node-pty": "1.2.0-beta.12"
</file context>
Fix with cubic

Effect.gen(function* () {
const http = yield* HttpClient.HttpClient
const apiKey = process.env.BROWSER_USE_API_KEY ?? ""
const endpoint = resolveEndpoint()

@cubic-dev-ai cubic-dev-ai Bot Sep 21, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: resolveEndpoint() throws when BCODE_FETCH_USE_ENDPOINT is invalid, and this runs at layer-build time unconditionally — even when the feature is disabled. The layer is composed into the tool registry unconditionally (packages/opencode/src/tool/registry.ts: layer: layer.pipe(Layer.provide(FetchUse.layer))), and the webfetch tool yields FetchUse.Service while defining itself (webfetch.ts:31), so any tool-registry build runs the validation. Before this PR a bad value was impossible; now a stale or unrelated BCODE_FETCH_USE_ENDPOINT hard-fails tool-layer construction for a user who may never use webfetch and holds no BROWSER_USE_API_KEY (in which case there is no key to leak, so fail-fast protects nothing here). Gate the validation on the feature actually being usable so the disabled case stays quiet, or confirm the wider blast radius is intended.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/bcode-browser/src/fetch-use.ts, line 35:

<comment>`resolveEndpoint()` throws when `BCODE_FETCH_USE_ENDPOINT` is invalid, and this runs at layer-build time unconditionally — even when the feature is disabled. The layer is composed into the tool registry unconditionally (`packages/opencode/src/tool/registry.ts`: `layer: layer.pipe(Layer.provide(FetchUse.layer))`), and the webfetch tool yields `FetchUse.Service` while defining itself (`webfetch.ts:31`), so any tool-registry build runs the validation. Before this PR a bad value was impossible; now a stale or unrelated `BCODE_FETCH_USE_ENDPOINT` hard-fails tool-layer construction for a user who may never use webfetch and holds no `BROWSER_USE_API_KEY` (in which case there is no key to leak, so fail-fast protects nothing here). Gate the validation on the feature actually being usable so the disabled case stays quiet, or confirm the wider blast radius is intended.</comment>

<file context>
@@ -32,11 +32,12 @@ export const layer = Layer.effect(
   Effect.gen(function* () {
     const http = yield* HttpClient.HttpClient
     const apiKey = process.env.BROWSER_USE_API_KEY ?? ""
+    const endpoint = resolveEndpoint()
     return Service.of({
       enabled: apiKey.length > 0,
</file context>
Suggested change
const endpoint = resolveEndpoint()
const endpoint = apiKey.length > 0 ? resolveEndpoint() : DEFAULT_ENDPOINT
Fix with cubic

// is a mistake rather than a default, because the default is the direct fetcher
// -- the exact path someone setting this variable is trying to leave.
function resolveEndpoint() {
const configured = process.env.BCODE_FETCH_USE_ENDPOINT

@cubic-dev-ai cubic-dev-ai Bot Sep 21, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: BCODE_FETCH_USE_ENDPOINT is new behavior that changes where the webfetch API key is sent (and that many values are rejected at startup), but it is documented nowhere in-tree. The README (packages/bcode-browser/README.md) and the opencode config docs (packages/core/src/v1/config/config.ts:187) describe only BROWSER_USE_API_KEY. Add a line to the README's contents/env section so operators know the variable exists and what values are accepted (https anywhere, http only to loopback).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/bcode-browser/src/fetch-use.ts, line 71:

<comment>`BCODE_FETCH_USE_ENDPOINT` is new behavior that changes where the webfetch API key is sent (and that many values are rejected at startup), but it is documented nowhere in-tree. The README (`packages/bcode-browser/README.md`) and the opencode config docs (`packages/core/src/v1/config/config.ts:187`) describe only `BROWSER_USE_API_KEY`. Add a line to the README's contents/env section so operators know the variable exists and what values are accepted (https anywhere, http only to loopback).</comment>

<file context>
@@ -56,4 +57,42 @@ export const layer = Layer.effect(
+// is a mistake rather than a default, because the default is the direct fetcher
+// -- the exact path someone setting this variable is trying to leave.
+function resolveEndpoint() {
+  const configured = process.env.BCODE_FETCH_USE_ENDPOINT
+  if (configured === undefined) return DEFAULT_ENDPOINT
+  if (configured.trim() === "")
</file context>
Fix with cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants