Repository navigation
Release BetterWright 2.9.1 with BetterChromium 156 - #242
Conversation
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.
Tested 1 flow, found no issues. What we tested
The pull request merged before TREX finished testing |
[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
SummaryReleases BetterWright 2.9.1 with BetterChromium 156.0.8078.90.
Reviews (1) · Last reviewed commit: "Make native browser test fixtures wait f..." · Reviewed by Greptile |
| + std::vector<V8ConsoleMessage> pending; | ||
| + pending.reserve(m_pendingReports.size()); | ||
| + for (V8ConsoleMessage* message : m_pendingReports) { | ||
| + pending.push_back(*message); | ||
| + } |
There was a problem hiding this comment.
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!
E2E review: PR #242 at
|
| 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 titleneedshttps://example.com, which this sandbox's network allowlist blocks (ERR_CONNECTION_CLOSED).native linux/win archive includes runtime files…:scripts/chromium/package-runtime.pycallshashlib.file_digest, which needs Python 3.11+. This host has 3.10.12. The script and test are unchanged frommain.protocol-mcp-browser-roundtripand-failure-recoveryfail only in the headed run: the harness passesDISPLAYthrough but not the X authority, so headed Chromium cannot open the display. WithDISPLAYunset, 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)
- V8 console UAF, 153 (current
mainbrowser) vs 156 (this PR). 153 shows "Aw, Snap!" andcrashed:true; 156 showscrashed:false, alive:true. MP4
- CLI flow on BetterChromium 156: version, navigate, form fill, persistence, private-network block, close. MP4
- MCP stdio E2E cases on 156, 8/8 passed. MP4
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);|
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
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 |
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:
release:checkpassed 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.