Conversation
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
There was a problem hiding this comment.
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) && |
There was a problem hiding this comment.
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>
| fetch: (url, { timeoutMs }) => | ||
| Effect.gen(function* () { | ||
| const request = yield* HttpClientRequest.post(ENDPOINT).pipe( | ||
| const request = yield* HttpClientRequest.post(endpoint).pipe( |
There was a problem hiding this comment.
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>
| "@sentry/solid": "10.36.0", | ||
| "@sentry/vite-plugin": "4.6.0", | ||
| "solid-js": "1.9.10", | ||
| "solid-js": "1.9.15", |
There was a problem hiding this comment.
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>
| Effect.gen(function* () { | ||
| const http = yield* HttpClient.HttpClient | ||
| const apiKey = process.env.BROWSER_USE_API_KEY ?? "" | ||
| const endpoint = resolveEndpoint() |
There was a problem hiding this comment.
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>
| const endpoint = resolveEndpoint() | |
| const endpoint = apiKey.length > 0 ? resolveEndpoint() : DEFAULT_ENDPOINT |
| // 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 |
There was a problem hiding this comment.
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>
Issue for this PR
Closes #
Type of change
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
If you do not follow this template your PR will be automatically rejected.