Skip to content

Release BetterWright 2.9.1 with BetterChromium 156 - #242

Merged
SSHdotCodes merged 7 commits into
mainfrom
release/2.9.1
Nov 4, 2026
Merged

SSHdotCodes merged 7 commits into
mainfrom
release/2.9.1

Conversation

@SSHdotCodes

@SSHdotCodes SSHdotCodes commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

Upgrade the managed browser from Chromium 153.0.8010.36 to 156.0.8078.90 and pin verified public macOS arm64, Linux x64, and Windows x64 runtime archives. This includes the Chromium security fixes reported in #240, the 55 Extended Stable 156.0.8078.88 fixes, and the native V8 deferred-console use-after-free correction.

Includes dsshap's Chromium 156 patch rebase from #241 with the original author attribution. Changes on top fix the Chromium 156 ICU locale setter compilation error, make deferred console delivery report message copies, queue only storage-owned messages, and stop delivery when storage is replaced. The build documentation records the exact depot_tools revision and platform prerequisites.

Native browser fixtures wait for the delayed image request to reach the server before asserting it is pending and terminate the owned CDP fixture process tree on Windows before profile deletion. These fixes preserve the assertions and remove request-arrival and process-cleanup races exposed by native validation.

Validation:

  • All three platforms passed 454/454 V8 inspector tests. The macOS archive reports 156.0.8078.90 and passes strict deep signature verification after extraction. All published asset digests match the source installer pins and SHA256SUMS.
  • Native validation: Windows and macOS each passed all 132 browser integration tests with zero failures, including recording. Windows DPAPI was verified before testing; cookie and profile restart persistence passed.
  • The same validation run passed real 2.9.0-to-2.9.1 upgrades on Linux, Windows, and macOS: public package/declaration compatibility, automatic browser replacement, retained HttpOnly cookies, localStorage, IndexedDB, vault credentials, and fingerprint seed.
  • Final required CI: all nine required jobs passed, including package integrity, all six cross-platform unit jobs, audit, the full Linux browser suite, and binary E2E.
  • Pinned Bun 1.4.0 release:check passed locally: 1,343 passing tests, 21 optional local browser/live-service skips, zero failures. Final fixture lint, harness typecheck, and 11 targeted native macOS tests also passed.

Closes #240.
Includes #241.

dsshap and others added 7 commits November 3, 2026 09:36
Fixes CVE-2026-118306, CVE-2026-118331, CVE-2026-118348, CVE-2026-118344
(fixed upstream in 156.0.8078.73; fork was pinned to 153.0.8010.36).

- New patches/chromium-156/: 27/33 Chromium hunks applied cleanly, 8
  hand-rebased for 156 source drift (ICU locale API moved to
  default_icu_locale.h/language_tag.h, image_data_buffer gained
  retained_image_/color-space checks, V8 already ships the
  V8_OS_LINUX rename so the wasm hunk is dropped as obsolete).
- Bump BETTERWRIGHT_CHROMIUM_VERSION, release tag r1, gclient-156.py,
  apply-patches.sh, Windows manifest, checksums, docs, tests.
- Asset SHAs are zero placeholders: zips are NOT built yet. setup/update
  fail closed until betterchromium-156.0.8078.90-r1 is built, packaged,
  published, and pinned here. Full build + package validation still needed.
Chromium 156 makes DefaultIcuLocaleSetterKey constructible only by a
fixed friend list, and ContentMainRunnerImpl is not on it, so the
rebased SetDefaultIcuLocale(DefaultIcuLocaleSetterKey(), ...) call in
content_main_runner_impl.cc could not compile. SetICUDefaultLocale
still exists in 156 and is friended; it sets ICU's process default and
base's cached LanguageTag, matching the shipped 153 behavior.

The renderer_main.cc call stays as rebased: RendererMain is a friend
and upstream 156 makes the same call there.
Bump the package to 2.9.1, record the Chromium 156 security upgrade in
the changelog, and update the docs that describe the current pin. The
V8 build note now reflects that upstream V8 15.6 carries the host-OS
guard the 153 V8 patch added.
The deferred console delivery added in the 153 V8 patch reported raw
pointers to storage-owned messages. Reporting can run page code through
custom object formatters, and console.clear() or eviction from that
code freed messages still being reported. Copy queued messages before
reporting, as upstream enable-time replay does (crbug.com/485672657),
and queue a message only after storage owns it, so eviction and clear()
remove every pending pointer before its message is freed.
Reporting a deferred batch can run page code that resets the context
group. Continuing the old batch then advanced the new storage's session
watermarks with stale sequence numbers and suppressed its first live
console events. Capture the storage identity before reporting, use only
locals afterward, and stop delivering once the storage is replaced,
matching upstream addMessage's reentrancy check.
@SSHdotCodes
SSHdotCodes marked this pull request as ready for review November 4, 2026 09:33
@greptile-apps

greptile-apps Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

RetriggerTREX TREX

Tested 1 flow, found no issues.

What we tested

  • Pass · Run pages in managed Chromium 156 · View ↗

The pull request merged before TREX finished testing a3c8e5f.

@SSHdotCodes
SSHdotCodes merged commit 7aee003 into main Nov 4, 2026
11 checks passed
@greptile-apps

greptile-apps Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The changes reach sessions that use BetterWright’s managed browser.

The PR appears safe to merge, with a non-blocking request to test the console lifetime fix directly.

Findings

  1. P2 Crash fix lacks direct coverage ▶

Summary

Releases BetterWright 2.9.1 with BetterChromium 156.0.8078.90.

  • BetterWright installs Chromium 156 as its managed browser.
  • Pages keep their selected identity settings on Chromium 156.
  • DevTools console messages arrive from deferred copies.
  • Browser tests wait for requests and stop their Windows child processes.

Reviews (1) · Last reviewed commit: "Make native browser test fixtures wait f..." · Reviewed by Greptile

Comment on lines +161 to +165
+ std::vector<V8ConsoleMessage> pending;
+ pending.reserve(m_pendingReports.size());
+ for (V8ConsoleMessage* message : m_pendingReports) {
+ pending.push_back(*message);
+ }

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 Crash fix lacks direct coverage

The new test resets the context group between completed evaluations. It never clears or evicts storage while FlushPendingReports reports a message, which is the failure these copies fix. Removing the copies could therefore bring back the use-after-free without failing the test.

Add a regression test that queues multiple messages and clears or evicts storage from a custom formatter during deferred delivery.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@devin-ai-integration

Copy link
Copy Markdown
Contributor

E2E review: PR #242 at a3c8e5f9e8b054cc16ae7848d0f8de697c7a6db7

The PR was merged (7aee003) while this review ran. The merge tree is identical to the tested head. No confirmed defects, so there is no fix PR.

Environment: Linux x64 (Ubuntu 22.04), Bun 1.4.0, playwright-core 1.61.1, base bed7d46. Managed browser installed by betterwright setup from betterchromium-156.0.8078.90-r1. Headed runs used X11; WebGL fell back to SwiftShader.

Journey Result
setup downloads the Linux archive, checks SHA-256 c67db909…, installs it; doctor reports BetterChromium 156.0.8078.90 and prints "BetterWright is ready" passed
Release assets: all three zips match the CHROMIUM_FORK_ASSETS pins and SHA256SUMS. The mac Info.plist reports 156.0.8078.90 and the Windows 156.0.8078.90.manifest matches the repo copy passed (mac/win checked by inspecting the archives, not run)
V8 deferred-console UAF: custom devtoolsFormatters call console.clear() and add messages while deferred reporting runs (CDP Runtime.enable + setCustomObjectFormatterEnabled) 153 renderer crashes 4/4 runs ("Aw, Snap!"); 156 survives 4/4 runs (7,260 console events, page stays responsive)
CLI on 156 (headed): run goto local fixture, then fill + click, state persists across calls, --block-private-network blocks 169.254.169.254 (ERR_BLOCKED_BY_CLIENT), sessions, close --all passed
MCP stdio server (betterwright mcp): initialize/tools, invalid requests, browser roundtrip with state kept across calls, failure recovery, download deny passed 8/8 (headless)
tests/node/browser.test.ts on real BetterChromium 156, including both changed fixtures (default navigation…subresources, spawnCdpEndpoint provider-chain tests) 131/132 passed; 1 environment failure (see below)
Binary E2E tests/e2e/run.ts (CLI/browser/security/protocol/lifecycle) 100/102 passed; 2 environment failures (see below)
lint, typecheck, check:versions passed
test:unit 1356 passed, 6 skipped, 2 environment failures (see below)

Environment failures (none caused by this PR):

  • navigate and read the title needs https://example.com, which this sandbox's network allowlist blocks (ERR_CONNECTION_CLOSED).
  • native linux/win archive includes runtime files…: scripts/chromium/package-runtime.py calls hashlib.file_digest, which needs Python 3.11+. This host has 3.10.12. The script and test are unchanged from main.
  • protocol-mcp-browser-roundtrip and -failure-recovery fail only in the headed run: the harness passes DISPLAY through but not the X authority, so headed Chromium cannot open the display. With DISPLAY unset, both pass (see the MCP video).

Optional hardening (not a bug): no repo test covers the deferred-console UAF fix. The repro below fails on 153 and passes on 156, so it could serve as a native regression test.

Not tested: native macOS/Windows execution (no such runners here; artifacts checked only by digest and metadata). Also untested: real cloud providers, Electron, cookie sync from personal browsers, model-backed exec, light/dark or mobile UI (no UI changes in this PR), and building BetterChromium from source.

Recordings (all at a3c8e5f)

  1. V8 console UAF, 153 (current main browser) vs 156 (this PR). 153 shows "Aw, Snap!" and crashed:true; 156 shows crashed:false, alive:true. MP4
    UAF 153 vs 156
  2. CLI flow on BetterChromium 156: version, navigate, form fill, persistence, private-network block, close. MP4
    CLI flow
  3. MCP stdio E2E cases on 156, 8/8 passed. MP4
    MCP E2E

The MP4 links are Devin attachments and may require Devin sign-in. The animated previews above are public copies of the same recordings.

UAF repro script (Playwright + CDP)
// Deferred-console reporting stress: custom formatters run page code during
// console reporting and clear/evict the message storage.
import { chromium } from "/home/ubuntu/repos/betterwright/node_modules/playwright-core/index.mjs";
const exe = process.argv[2];
const rounds = Number(process.argv[3] || 30);
const headless = process.env.HEADED ? false : true;
const withTimeout = (p, ms) => Promise.race([p, new Promise((_, rej) => setTimeout(() => rej(new Error("timed out after " + ms + "ms")), ms))]);
const browser = await chromium.launch({ executablePath: exe, headless, args: ["--no-sandbox"] });
let crashed = false, consoleEvents = 0;
const ctx = await browser.newContext({ viewport: { width: 900, height: 600 } });
const page = await ctx.newPage();
page.on("crash", () => { crashed = true; console.log("PAGE CRASHED"); });
page.on("console", () => { consoleEvents++; });
const cdp = await ctx.newCDPSession(page);
await cdp.send("Runtime.enable");
await cdp.send("Runtime.setCustomObjectFormatterEnabled", { enabled: true });
console.log("browser", browser.version());
await page.setContent(`<h1 id=h>Deferred console stress</h1><pre id=s></pre>`);
for (let r = 0; r < rounds && !crashed; r++) {
  try {
    await withTimeout(page.evaluate((r) => {
      let depth = 0;
      window.devtoolsFormatters = [{
        header(obj) {
          if (!obj || !obj.__bw) return null;
          depth++;
          if (depth < 50) {
            console.clear();
            for (let i = 0; i < 40; i++) console.log("filler " + i, "x".repeat(2000));
            console.log({ __bw: true, nested: depth });
          }
          return ["span", {}, "formatted " + depth];
        },
        hasBody() { return false; },
      }];
      for (let i = 0; i < 200; i++) console.log({ __bw: true, i }, "msg " + i);
      document.getElementById("s").textContent = "round " + r + " queued";
    }, r), 20000);
    await page.waitForTimeout(50);
  } catch (e) { console.log("round", r, "error:", String(e.message).split("\n")[0]); if (page.isClosed() || crashed) break; }
}
let alive = false;
if (!crashed) try { alive = (await withTimeout(page.evaluate(() => 1 + 1), 5000)) === 2; } catch {}
console.log(JSON.stringify({ exe, rounds, crashed, alive, consoleEvents }));
if (process.env.HEADED) await new Promise((r) => setTimeout(r, 3000));
await withTimeout(browser.close(), 5000).catch(() => {});
process.exit(crashed || !alive ? 1 : 0);

Written by Devin

@devin-ai-integration

Copy link
Copy Markdown
Contributor

I found no bugs in PR #242 at a3c8e5f, so there's no fix PR. It was merged while I tested; the merged code is identical to what I tested. E2E report

  • V8 console crash: I reproduced it on the old 153 browser (4/4 renderer crashes); 156 survives 4/4.
  • CLI, MCP, browser and binary E2E suites: passed, except 5 failures from this sandbox (blocked example.com, Python 3.10, headed X auth).

Not tested: native macOS/Windows runs (archives checked by hash only). The MP4 links need Devin sign-in; the animated previews are public.

This response includes attachments — view them in Devin

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.

Pinned BetterChromium 153.0.8010.36 affected by Chromium 156 security fixes

2 participants