Vulnerability Research

The Promise Arrived After the Timeout Bell—Inside Chromium's reportWin UAF (CVE-2026-15133)

CVE-2026-15133 began after a Protected Audience winner was chosen: a report left browserSignals on globalThis , queued a Promise to read renderUrl , and timed out; native helpers left with the stack, then cleanup ran it through a stale handle into an expired object, so the fix kept the filler through scope cleanup, limited logger use to script execution, and reset native links before delayed JavaScript resumed.

Hand-drawn auction reporting room after a red timeout bell has rung; native helpers are leaving while a blue browserSignals envelope and a Promise claim ticket remain connected to a brass property drawer.
In this article

Research basisThis report follows the same queued Promise ticket through Chromium commit 5a68bc6c97d97312ffa0f9ac04e2288f1cdadd13 , the M150 backport, all six changed files, and the upstream regression, keeping source-derived object order, actual test assertions, and Chrome product findings aligned. The Chrome CNA sets the impact at arbitrary code execution inside a sandbox through crafted HTML; the Stable bulletin, VersionHistory, and Included-In records establish the fixed platform builds. At this review date, the public record shows neither exploitation in the wild nor a sandbox escape.

SourceChrome security bulletin / Chromium auction_worklet source and regression tests / CVE and NVD / SOSEC source review

1 After the auction was won, the reporting room gained a drawer that did not open immediately

1.1 The browserSignals envelope carried a normal field and one legacy handle

The story begins after a Protected Audience auction has already chosen a winner. Chromium runs bidder and seller JavaScript inside auction worklets. Once a bidder wins, BidderWorklet::V8State::ReportWin() assembles the arguments and calls the bidder's reportWin() function. This stage no longer decides the auction. It lets the winning bidder register a report URL, beacons, Private Aggregation work, and other permitted post-auction output.

One argument is the browserSignals object, an envelope of facts supplied by the browser instead of the page. To JavaScript, its properties look like ordinary values. Inside Chromium, however, those properties are assembled through several C++ paths. Some are written into the V8 object immediately; others are represented by native getters that do not run until the script asks for them.

The render address appeared under two spellings. The recommended renderURL field was populated synchronously, like a card already sealed inside the envelope. The deprecated renderUrl alias was installed through DeprecatedUrlLazyFiller::AddDeprecatedUrlGetter(). It was a brass drawer beside the envelope: the contents and warning were supplied only when somebody pulled the handle.

That capitalization difference is the entrance to the entire defect. Reading renderURL does not need the stack-local deprecated filler. Reading renderUrl crosses into a native property callback. A queued Promise, a reporting timeout, and C++ stack unwinding matter only because the old alias preserved that callback route. Treating both spellings as one generic URL field erases the decisive lifetime transition.

The lazy path had a sensible compatibility purpose. Chromium could avoid doing work for a field that no script used, and the first access could emit a deprecation warning telling developers to migrate. The filler therefore held pointers to the URL and warning text, plus a temporary connection to an AuctionV8Logger. V8 callback data retained the route back to that helper.

Synchronous access kept everyone in the room. A bidder read renderUrl during reportWin(); the filler was still on the stack, the logger still belonged to the active context, and the getter could log once before returning the URL string. The old implementation appeared dependable because this common path nested all three lifetimes neatly.

JavaScript was not required to read the property immediately. It could save the whole object on globalThis, close over it, or postpone the read with a Promise reaction. The V8 object and queued job then remained reachable after the native call started failing. The helper behind the drawer was still only a local C++ object in one invocation of ReportWin().

The CNA description says a crafted HTML page can reach the issue, but that does not mean every ordinary tab naturally walks into this reporting stage. A deployment must run the relevant Protected Audience auction, execute bidder reporting, retain the object, select the deprecated getter, and cross the timeout cleanup sequence. Exposure analysis therefore needs product capability, process, and build evidence alongside web reachability.

Bidder reportWin() also needs to stay distinct from seller reportResult(). Both use auction-worklet infrastructure and both appear in the six-file patch, yet the published regression and direct UAF repair sit on the bidder path. Seller changes remove a misleading logger parameter from a lazy-filler construction route; they do not establish a second trigger for this CVE.

Fields sharing one JavaScript object do not automatically share one native implementation. SetBrowserSignals() writes some values directly and asks specialized fillers to install others. Source review therefore follows each property spelling to its assignment or getter registration. The object name alone cannot prove common caching, callback data, warning behavior, or lifetime.

The “first access” rule also explains quiet telemetry. A bidder that never reads renderUrl never invokes the handler and never generates its warning. The regression arranges for cleanup to perform the first read, turning deferred compatibility work into a native entry point at the least forgiving moment.

Keep four objects on the reporting desk as the investigation moves forward: the blue browserSignals envelope, the deprecated getter's brass drawer, a Promise claim ticket waiting in the microtask queue, and the red reporting-timeout bell. Nothing exotic is added later. The vulnerability emerges solely from the order in which those familiar objects are allowed to remain.

1.2 JavaScript, the filler, and the logger kept different clocks

The first clock belonged to V8. As long as browserSignals remained reachable from a global or closure, the JavaScript object could survive. As long as a Promise reaction remained queued, it could run at a future checkpoint. The garbage collector understood JavaScript reachability; it did not know that callback metadata contained the address of a C++ local variable.

The second clock belonged to DeprecatedUrlLazyFiller. The affected function placed it inside the current ReportWin() stack frame. Its lifetime ended according to C++ declaration order when control left that scope. Putting its address inside v8::External did not create ownership, reference counting, or a request for the compiler to delay destruction.

The third clock belonged to the stack-local AuctionV8Logger. The old filler borrowed it from construction onward so the deprecated getter could send a console warning to the current V8 context. That loan was valid while the reporting script was active. A failure path that entered teardown needed a fresh answer about whether the logger still existed.

A fourth object determined the final calling moment. ContextRecyclerScope acquired the short-lived context at construction and invoked ResetForReuse() in its destructor. Reset did not merely release passive data. It could execute JavaScript by performing a microtask checkpoint. A cleanup guard that looked like ordinary RAII therefore controlled the last chance for a queued getter to run.

The clocks appeared synchronized on a successful call. A normally returning script gave V8 an opportunity to process ready microtasks while the local helper and logger were intact. Timeout broke that illusion: the automatic checkpoint was skipped during termination, the job stayed queued, and the C++ function continued toward destruction.

No two operating-system threads had to race over the pointer. The entire sequence could occur on one checked Chromium sequence and within one isolate: V8 entered termination, C++ unwound locals in reverse order, and the scope destructor later asked the recycler to perform the missing checkpoint. The two runtimes simply disagreed about when the invocation had truly finished.

A null check on the logger alone could never repair that disagreement. The callback first had to recover a valid filler object before it could read self->v8_logger_. If the filler lifetime had ended, evaluating the member was already invalid. The direct correction needed to keep the filler alive across the drain and then define exactly when its shorter-lived logger connection was present.

An object ledger makes the mismatch concrete. The V8 object is owned by the context and can outlive the native call. The Promise job is owned by the microtask machinery and can remain ready but unexecuted. The deprecated filler is stack storage whose address was exported. The per-call logger is another stack object borrowed by that filler. The scope is not referenced by the getter, yet it owns the last checkpoint.

The worklet process model adds investigative distance. A page initiates the auction, but bidder JavaScript, getter callback, and logger state live in the service process hosting the worklet context. Endpoint collection that captures only a tab or installer event can miss the process where the checkpoint and invalid access occur. Process type and build ID must travel with the crash.

The URL's underlying storage should also be kept separate from the filler that stores its pointer. Public source already proves that the callback enters an ended filler and reads members from it. An analyst does not need to claim the GURL bytes were overwritten at the same instant. Stopping at the first invalid object access produces a stronger, narrower finding.

The fixed source also clarifies that this particular recycler is local to ReportWin(). A comment calls the context short lived so global data cannot leak between repeated calls to the worklet or into another worklet. The name ResetForReuse() comes from the class interface; this CVE completes inside the current teardown and does not require a later auction to receive the same context.

Hand-drawn lifetime tracks show a V8 envelope moving beyond the timeout bell while the wooden filler and green logger lamp end earlier.
Figure 1. The V8 object, stack filler, and logger had separate ending times. The marks on the upper track show time passing, not four source objects; the queued work traveled furthest without extending the native objects behind its getter.

2 The Promise left a claim ticket, and timeout dismissed only the staff behind the counter

2.1 The upstream regression arranged four moves along one repeatable path

The new test is named BidderWorkletTest.ReportWinBrowserSignalRenderUrlDeprecationAsync. It does not depend on a sprawling auction or a lucky scheduling race. Its script saves an object, queues one Promise reaction, reads the old getter inside that reaction, and then loops until the reporting time limit terminates execution.

First, browserSignals is assigned to globalThis.savedBrowserSignals. That global reference keeps the same V8 object reachable after the function's parameter scope has ended. The test does not claim that a global variable is the only possible retention method; it chooses a stable, easily inspected route.

Second, Promise.resolve().then(...) schedules a reaction that evaluates void globalThis.savedBrowserSignals.renderUrl. The return value is intentionally ignored. What matters is that the property read enters the native lazy getter, while the void form avoids creating unrelated report output.

Third, while (true) {} holds execution until the bidder reporting limit is reached. The loop is a deterministic laboratory timer. Real content does not need to contain that exact source text, and the string is not a useful network indicator. It merely forces the engine into the termination state that postpones the reaction.

CreateReportWinScript(kBody) places that body inside reportWin(), so the busy loop occurs during the function call stage. A separate upstream test named ReportWinTopLevelTimeout covers an infinite loop in the top-level worklet script and expects the exact error https://url.test/ top-level execution timed out. The two stages share timeout machinery, but they enter different output branches.

The fourth move is native cleanup. The reaction is ready but has not run. Chromium records the call failure, begins returning through C++, destroys later locals first, and eventually reaches the scope destructor. That destructor asks the recycler to flush the job queue, sending the claim ticket back through the getter.

The fixture had not registered a report URL, beacon, macro, Private Aggregation request, or Private Model Training payload before it looped. The helper therefore expects a null URL, empty beacon and macro maps, an empty PA vector, and a null PMT pointer. Those five empty results describe this script. They do not establish a product rule that every timed-out script loses PA work already produced.

The exact expected error is https://url.test/ execution of `reportWin` timed out., and the reporting-latency timeout flag must be true. The source URL is fixture provenance, not an indicator of compromise. In production, the meaningful fields are the bidder script identity, execution stage, timeout outcome, and native process that handled the worklet.

Promise.resolve() is already fulfilled, yet its then handler does not run inline with the current JavaScript statement. It is enqueued as a microtask. A plain synchronous property read would finish before the infinite loop and miss the destructor sequence. The language's scheduling rule supplies the delay without requiring a timer, network callback, or second thread.

Saving the argument globally does more than lengthen its lifetime. It gives the reaction a stable route to the same object after timeout has torn down the reporting call's lexical parameter scope. Cleanup therefore cannot assume that dropping C++ argument containers makes the V8 object unreachable; script-created global state needs to be considered explicitly.

This TEST_F contains no literal EXPECT_NO_CRASH, and it does not assert a warning or returned URL. Successful completion plus exact typed results demonstrate that the repaired implementation survived the delayed getter. Adjacent synchronous tests retain responsibility for warning compatibility and the preferred field.

2.2 V8 skipped its ordinary checkpoint while execution was terminating

V8 normally runs ready microtasks at policy-defined checkpoints as JavaScript returns to its host. On a successful bidder report, the queued reaction would typically run while the filler and logger still belonged to the active script interval. Timeout takes a special route because the isolate is terminating execution.

The regression comment states the key engine behavior directly: V8's normal automatic microtask checkpoint is skipped while execution is terminating. The Promise does not vanish and its handler does not run synchronously. It remains in the isolate's queue until the host performs a later checkpoint.

This separates “the script failed” from “all work scheduled by the script has been drained.” The worklet can correctly return a timeout result while a reaction from the failed script is still ready. Any cleanup routine that deliberately flushes such a queue must assume it can reenter every native callback that the context still exposes.

The old manual checkpoint had a legitimate purpose. Leaving reactions behind as the context shuts down could violate isolation and cleanup expectations. ResetForReuse() therefore tried to flush the queue as a normal return would. The defect was not that microtasks ran; it was that some callback data had already outlived its native owner by the time they ran.

The ordering is deterministic enough to test. Termination suppresses the automatic checkpoint, local C++ objects unwind in language-defined reverse order, and scope destruction performs the manual checkpoint. A scheduler does not have to select a narrow cross-thread window. Implementation details can move, but the safety property remains: wherever a delayed job resumes, its native owner must still exist or its connection must have been withdrawn.

The checkpoint can drain more than the one reaction in the unit test. Other ready handlers may execute, and a handler may enqueue additional work according to V8 policy. That is why the hardening moves the entire block of recycler-owned resets ahead of the checkpoint. The stack-local deprecated filler receives its own direct lifetime repair because it is not a member of that reset list.

AuctionV8Helper::TimeLimitScope still wraps the manual checkpoint. Chromium did not trade lifetime safety for unlimited teardown execution. Deferred JavaScript remains resource-controlled; the change is that every native link it can reach is either valid or intentionally reset during that interval.

Hand-drawn five-step sequence moves from a browserSignals envelope to a Promise ticket, a timeout bell, departing helpers, and the same ticket reaching for an open brass drawer.
Figure 2. Timeout did not cancel the queued reaction. It removed the ordinary checkpoint, so the same claim ticket reappeared only during native teardown.

3 C++ unwound locals in reverse construction order, and the last scope to leave ran JavaScript again

3.1 The old ReportWin declared the helper that needed to live longest too late

Before the fix, ReportWin() entered a full isolate scope, constructed a ContextRecycler, and then constructed ContextRecyclerScope. The scope obtained the V8 context and promised to reset it from its destructor. The object responsible for the final cleanup was therefore created early in the stack frame.

The function later created a stack-local AuctionV8Logger, assembled signals and arguments, and only then declared DeprecatedUrlLazyFiller. The helper received the URL and warning, borrowed the logger, and installed the getter for renderUrl.

C++ destroys successfully constructed locals in reverse order. The late deprecated filler ended first. The logger ended after it. The earlier ContextRecyclerScope ended later and entered ResetForReuse(). This ordering follows directly from the fixed and parent snapshots; it does not require a private crash dump to establish.

Normal execution hid the flaw. If no reaction would read the getter after the reporting function returned, allowing the filler to die before the scope appeared harmless. Timeout left exactly such a read behind, converting an ordinary declaration choice into an invalid reentry during destruction.

The old filler stored borrowed pointers to the logger, GURL, and warning text. A surviving pointee would not rescue the filler itself. The handler first had to use the expired this pointer to locate those members. The first invalid operation therefore precedes any question about whether a URL buffer had also been overwritten.

The parent of the main fix, ce8e75161d0388fa68ebc01a48cbdaa04d863d94, is useful as a pre-fix source snapshot, not as a regression origin. Its subject concerns an unrelated Glic change. Public evidence does not identify the introducing commit or the first affected Chrome release, so a neighboring Git parent must not be promoted into that role.

Likewise, the June 26 main commit date tells us when the corrective logic entered Chromium main. It cannot prove every historical build before that date was vulnerable. Product affected ranges come from vendor records, and an older downstream branch needs its own source comparison or advisory.

Object destruction is not memory zeroing. A stack slot can retain bytes that resemble the former filler, and an uninstrumented run may still print a warning or URL. A later build, optimization level, or log path can reuse the same slot and crash. The C++ object has ended in every case, regardless of whether stale bytes look plausible.

3.2 ResetForReuse used to turn the microtask tray before folding away native links

ContextRecyclerScope::~ContextRecyclerScope() is short: it calls context_recycler_->ResetForReuse(). That brevity conceals substantial effects. Reset touches bindings, lazy fillers, and the V8 microtask queue while a context that has just executed untrusted script is leaving its scope.

In the vulnerable version, reset first created a TimeLimitScope and called PerformMicrotaskCheckpoint(). In the regression, that call supplied the checkpoint V8 skipped during termination. The saved Promise reaction ran immediately from the scope destructor.

Only afterward did the old function reset every object in bindings_list_, followed by bidding-browser-signal, interest-group, seller-browser-signal, report-win-browser-signal, and auction-config fillers. The delayed script therefore resumed while the previous invocation's recycler-owned native connections had not yet been uniformly withdrawn.

The deprecated URL filler was not part of that owned reset inventory. It was a local in ReportWin(), and its direct failure came from ending before the scope destructor. Moving the reset block alone would not unregister its getter. The repair needed both the local declaration move and the broader recycler ordering change.

The reentry chain is now complete: scope destruction calls reset; reset drains a Promise; the reaction reads a still-reachable V8 object; the property metadata produces an address; that address names a filler whose lifetime had ended. The queue and JavaScript object were valid. The native support behind their callback was not.

The fixed source calls the context short lived. ContextRecycler is local to this invocation, created so global data cannot bleed into later calls or other worklets. Even though the object will be destroyed after the function, its scope still performs the checkpoint during the current teardown. No future auction is required for the UAF.

Fixed ResetForReuse() now resets the binding registry and each optional filler before opening a time-limited checkpoint. The internal reset sequence remains stable; the whole block moved. That reduces behavioral drift while establishing a clear dominance rule: all native disconnect work must happen before any delayed JavaScript can resume.

Hand-drawn cutaway shows context, scope, filler, and logger nested together; after timeout two helpers leave before the scope turns a microtask carousel back toward their empty positions.
Figure 3. The old unwind removed the filler and logger before the later scope destructor performed its checkpoint. The microtask returned to a place where its registered helper no longer existed.

4 The envelope survived while the stack object behind its handle did not

4.1 A V8 External stores an address without extending a C++ lifetime

LazyFiller::DefineLazyAttribute() installs a lazy data property through V8 and passes callback data containing v8::External(this) plus an external-pointer type tag. Registration copies neither the URL nor the warning and creates no C++ owner. It answers which kind of helper to recover later, not whether that instance remains alive.

On first access, HandleDeprecatedUrl() invokes GetSelf<DeprecatedUrlLazyFiller>(info). The type tag prevents silently interpreting another filler class as this one. It supplies no ownership, epoch, or destructor notification. In this incident the type remained correct; the instance had expired.

The old handler then unconditionally called self->v8_logger_->LogConsoleWarning(self->warning_.get()). Merely reading v8_logger_ requires a live self, so the invalid access is established before the logger method. The handler subsequently obtains the V8 helper, calls self->url_->spec(), converts the string, and sets the property result.

AddDeprecatedUrlGetter() does nothing when its URL pointer is null. When a URL exists, it defines the lazy property and returns success or failure. A successful conversion returns a V8 string; a failed conversion sets V8 null. Crash triage should not assume every access logs, returns a URL, or stops at the same native instruction.

This is consequently not a cosmetic deprecation-warning bug. The warning call is an obvious early dereference, while the same accessor also supplies a security-relevant browser signal. Chromium's commit title names the deprecated URL handler lifetime, and the Chrome bulletin classifies the product issue as an InterestGroups use-after-free.

The generic LazyFiller header states the contract unusually plainly: an associated V8 context must be destroyed immediately after the filler to avoid UAF; destroying the context first would be better, but subclasses may borrow context-scoped objects such as AuctionV8Logger that must outlive the filler. The old layout satisfied neither safe ordering during the delayed checkpoint.

The patch adopts the preferred arrangement. The deprecated filler is constructed before the recycler and scope, so reverse destruction completes scope cleanup and context work while the filler remains valid. A separate detach handles the shorter logger lifetime. Extending the filler only through teardown is more precise than turning all of its borrowed per-call data into arbitrarily long-lived heap state.

The repaired logger null check is safe because declaration order first guarantees a live filler. Detach then writes null into a valid member, and a delayed getter can read that state. It is not an attempt to recover a convenient zero from expired stack bytes.

4.2 The confirmed impact stops at code execution inside a sandbox

Google's July 8, 2026 desktop Stable bulletin lists CVE-2026-15133 as High, describes a use-after-free in InterestGroups, and associates it with bug 527406824. It credits Jihyeon Jeong with reporting the issue on June 24 and records a $500 reward. Those fields identify the case; they are not substitutes for a technical impact statement.

The Chrome CNA states that versions before the normalized 150.0.7871.115 product range could allow a remote attacker to execute arbitrary code inside a sandbox through a crafted HTML page. “Inside a sandbox” is part of the official sentence and needs to survive summaries, translations, and incident records.

Public evidence does not establish a sandbox escape, cross-origin data access, persistence, account takeover, or compromise of a named advertising platform. The Stable notice does not announce exploitation in the wild. The CISA ADP container in the CVE record marked SSVC exploitation as none at its update time; that is a time-scoped assessment, not a permanent guarantee.

NVD displays a CVSS 3.1 score of 8.8 High from the CISA-ADP secondary provider. It is not a Chrome CNA score. A defensible advisory can show Google's High severity and the secondary numerical score together as long as each is attributed to its source.

Turning a stack UAF into reliable code execution depends on compiler configuration, stack-slot reuse, control-flow protections, the process model, and attacker influence over memory. The CNA conclusion is enough to justify urgent patching. The public record does not offer a stable memory layout, exploitation recipe, or success rate that could support further claims.

Sandboxed code execution is still a native control-flow and data-integrity failure inside a browser service process. Isolation does not reduce it to an ordinary script exception. If an investigation discovers host-level or cross-process effects, those consequences need their own evidence chain instead of being silently attached to this CVE.

Local priority should combine the actual Chromium revision, whether Protected Audience is compiled and enabled, bidder provenance, persistence of worklet-hosting processes, and update cadence. Ad-tech laboratories, kiosks, custom embedders, and browser automation may exercise reporting far more frequently than an ordinary desktop.

The evidence already sets the response priority: a remotely delivered native memory-safety defect with CNA-confirmed code execution inside a sandbox warrants immediate Stable deployment. Source revision, platform release, live-process identity, and the causal regression provide the shortest route from that priority to a verifiable closure decision.

Hand-drawn blue envelope remains lit while its callback line crosses a fading wooden helper, an empty green lamp stand, and torn fibers before reaching the brass drawer.
Figure 4. A reachable JavaScript object and recoverable callback address prove only that the address can still be found. They do not prove that a C++ object still lives there.

5 The patch lengthened the filler lifetime and shortened the logger loan

5.1 The helper moved before the scope, and the logger connected only while scripts could use it

Main commit 5a68bc6c97d97312ffa0f9ac04e2288f1cdadd13 changes the declaration order first. Immediately after obtaining the isolate, fixed ReportWin() constructs DeprecatedUrlLazyFiller deprecated_render_url. Only then does it construct ContextRecycler and ContextRecyclerScope.

A source comment says the filler needs to outlast the scope and cites bug 527406824. Reverse destruction now lets the scope finish ResetForReuse() and its checkpoint, then lets the recycler end, and only afterward destroys the earlier filler. A delayed getter reaches a valid this.

Keeping the filler alive would still leave the old logger loan unsafe. The patch removes the logger from the filler constructor and initializes v8_logger_ to null. URL and warning pointers remain under the existing lifetime contract; logger availability becomes an explicit state.

After arguments and direct-from-seller signals are ready, the function calls deprecated_render_url.SetLogger(&v8_logger). The instrumentation breakpoint beforeBidderWorkletReportingStart, top-level RunScript(), and reporting calls follow. Returns during earlier JSON or signal construction never attached the logger and therefore require no detach.

A non-positive explicit reporting timeout returns even earlier, before isolate, filler, or recycler creation. The callback reports reportWin() aborted due to zero timeout., a true timeout flag, and zero elapsed time. That path is not the Promise teardown trigger and should be separated from a script that consumes its execution budget.

Argument conversion failures occur after the scope and filler exist but before attachment. Invalid auction, per-buyer, or seller JSON returns empty output with timeout false. A failure while populating direct-from-seller signals can return conversion errors collected so far. Since the logger member is still null, scope teardown is safe without a detach call.

The first post-attachment exit is top-level worklet-script failure. Fixed code clears the logger, ends tracing, and returns a null report URL, empty beacon, macro, and PA collections, null PMT data, and a timeout flag derived from RunScript(). The bidder reporting function has not yet run.

Only after top-level success does Chromium add report, beacon, macro, Private Aggregation, and optionally Shared Storage bindings. It then invokes either reportWin or reportAdditionalBidWin. If that call fails or times out, code first detaches the logger, clears URL, beacon, and macro output, yet takes PA requests already produced. A source comment says scripts may use PA to detect timeout or failure.

The conditional function name matters for evidence. The published TEST_F directly exercises reportWin. Fixed source shows reportAdditionalBidWin shares the same arguments, deprecated getter, total time limit, and cleanup branch. That is source-proven coverage, not a claim that an additional-bid async test exists.

Successful reporting may continue into queueReportAggregateWin and reportAggregateWin. Chromium makes the Private Model Training binding available only when modelingSignals, joinCount, and recency were not read during reportWin() and the payload stays within its declared length. It then prepares the aggregate arguments and may call reportAggregateWin.

Once that binding exists and the aggregate function is called, Chromium preserves the request shape to avoid leaking one bit through whether a request appears. It can send an empty payload when sendEncryptedTo() was not called, when the function errors, or when it times out; an overlength payload is likewise cleared and an error is recorded. These semantics explain why successful cleanup cannot simply close every output binding as soon as reportWin() returns.

The success path therefore keeps the logger attached through all allowed reporting work, then clears it whether or not a report URL was supplied. Output is collected only after detach. The loan covers the useful script interval without extending into the scope's cleanup checkpoint.

The getter now logs only when v8_logger_ is non-null. A synchronous legacy access still produces its warning. A delayed cleanup access can return the URL from the live filler without entering a logger that belongs to finished script work.

The destructor adds DCHECK_EQ(v8_logger_, nullptr). This is a debug invariant, not a release-mode safety mechanism. Declaration order keeps the filler alive, explicit detach closes the loan, and the getter guard defines behavior after closure. The DCHECK catches a future control-flow exit that violates those rules.

The header documents both supported logger states: the logger outlives the filler, or it is cleared before it leaves. ReportWin() uses the second form because a per-call logger has no reason to cover context teardown. A partial backport that moves the filler but retains the permanent logger pointer remains unsafe.

Partial repairs fail in recognizable ways. Moving only the filler preserves a path into the expired stack logger. Adding only the logger guard still reads that member through an expired filler. Adding only the DCHECK changes no release behavior. Detaching on one error branch leaves later exits capable of carrying the old address into the checkpoint. Backport sign-off must cover the complete state machine.

Top-level RunScript(), the reporting CallFunction(), and optional aggregate call share one object named total_timeout. The busy loop consumes the full bidder-reporting budget; entering reportWin() does not create a fresh timer. Chromium also normalizes an explicit reporting timeout or the default script limit for browserSignals, so test evidence should record whether the caller supplied a value and what value was used.

The function begins with DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_). That fixed-source check reinforces the mechanism: Promise scheduling, termination, C++ unwind, and manual drain already form the failure on one sequence. No speculative second-thread race is needed.

5.2 The recycler folded native connections before draining the queue, and seller code shed a misleading logger loan

The second layer changes ContextRecycler::ResetForReuse(). The reset operations remain in their established internal order, but the whole block now runs before PerformMicrotaskCheckpoint(). Any delayed JavaScript resumes only after recycler-owned state from the invocation has been reset.

This hardening is separate from the stack filler correction. deprecated_render_url is not in the recycler's filler members. Its lifetime is protected by declaration order. Reset-first protects bindings and owned fillers that remain associated with the V8 context.

ContextRecycler::GetContext() also creates a recycler-owned AuctionV8Logger the first time it creates its context. That logger is distinct from the stack-local v8_logger attached to DeprecatedUrlLazyFiller. Several bindings and auction-config fillers borrow the recycler-owned instance. Confusing the two would obscure which cleanup rule applies.

AddBindings() requires an existing context, attaches a binding to it, and records a raw pointer in bindings_list_. The concrete objects remain owned by recycler unique_ptr members. Iterating the registry before the checkpoint therefore resets every binding exposed to the script without changing ownership.

Auction-config fillers have another source-level constraint. Their vector may grow when one worklet serves auctions with different limits, but it never shrinks because pointers to later entries may still be floating. The fixed path resets each entry before draining; it does not try to make stale addresses safe by deleting the vector.

Seller worklet code was cleaned in the same commit. AppendAuctionConfig() lost an unused short-lived AuctionV8Logger* parameter, and recursive component-auction calls were updated. ScoreAd() and ReportResult() stopped creating locals whose only apparent purpose was that parameter.

The actual AuctionConfigLazyFiller already used the recycler-owned logger. The removed argument was not used to construct it. The diff therefore demonstrates preventative interface cleanup, not a confirmed seller-side UAF. It makes the ownership model visible and reduces the chance of a future borrower choosing the wrong logger.

The patch spans six files with 65 insertions and 29 deletions. Bidder worklet and deprecated filler files implement the direct repair; the bidder unit test encodes the causal sequence; context recycler and seller worklet changes provide broader cleanup. A conflict resolution that drops one group should be recorded as a deviation, even if the resulting line count differs on a downstream branch.

The main change landed on June 26 as main@{#1653488}, Gerrit 8006698, Change-Id I570a1267be21d9d398c4e2102e21a6bb923345c2. The M150 backport is 4e15f05d550a3c0f7268a11e85427b0c6330e525, landed July 1 on branch-heads/7871. Those identifiers connect source review to release evidence without assuming every product uses the same hash.

Backport review should test two graph properties. Every path from logger attachment to scope exit must pass through SetLogger(nullptr). Every recycler-owned reset must dominate the checkpoint. These properties survive refactoring better than a search for a particular line number.

The fixed commit's note that other lazy fillers “seem fine” after code reading is an upstream review observation, not a proof about every future subclass. Reset-first has independent value because a newly added filler inherits a safer teardown order. It reduces reliance on each feature author remembering that a destructor can drain arbitrary ready JavaScript.

Declaration-order fixes are especially vulnerable to merge conflict damage. A downstream integrator can preserve the same constructor text but place it back near argument assembly, silently restoring the old reverse-destruction order. Review must inspect the final file location and scope, not merely confirm that newly added lines exist.

Hand-drawn two-layer repair keeps the helper outside the context cleanup, unplugs a green logger cable, and folds four schematic connectors before a microtask carousel starts.
Figure 5. The direct repair keeps the filler alive across scope teardown and limits the logger loan to script work. The second layer resets recycler-owned links before the checkpoint. The four connectors on the right are schematic and do not count source objects.

6 Regression must preserve timeout semantics, and releases must prove delivery per platform

6.1 Five output groups stay empty while the timeout and single expected error remain intact

The async regression binds memory safety and product semantics in one result. A vulnerable build can reenter the expired filler during scope teardown. A fixed build must return normally while still reporting that the invocation timed out. Avoiding the getter by pretending the script succeeded would fail the contract.

RunReportWinExpectingResult() expects std::nullopt for the report URL, separate empty maps for beacons and macros, an empty vector of PA requests, and nullptr for PMT data. These C++ types are checked independently. Typed comparison can expose binding-layer drift that a single serialized “no result” value would hide.

The helper also requires expected_reporting_latency_timeout to be true and compares the complete error vector. It verifies script_timed_out and checks that reporting latency reaches at least ninety percent of the explicit reporting timeout or the default script limit. The ratio accommodates scheduling variation while proving the script actually reached its time budget.

The empty PA vector needs special care. The fixed failure branch intentionally preserves PA requests created before a reporting function failed or timed out. This fixture made none, so empty is correct. A downstream test that creates a request before its loop should expect the product's preservation behavior, not copy the empty vector blindly.

Warning behavior is covered by ReportWinBrowserSignalRenderUrlDeprecationWarning. That test connects inspector support, runs sendReportTo(browserSignals.renderUrl), verifies a successful URL and no timeout, then expects the complete text browserSignals.renderUrl is deprecated. Please use browserSignals.renderURL instead. It also checks a one-frame stack attributed to reportWin, the bidder URL, and source line 10, followed by no more console events.

ReportWinBrowserSignalRenderUrlNoDeprecationWarning uses the same inspector approach with renderURL. It expects the URL, no timeout, no errors, and no console event. The two spellings therefore retain separate compatibility baselines. The async test can focus on lifetime without pretending to assert inspector output it never collects.

Comments beside the synchronous tests note that they can be removed when the deprecated field itself is removed. Until that product change happens, a security backport must preserve the old getter's first-warning and URL behavior. Replacing it with an eager ordinary value might make the UAF regression stop exercising its callback while changing compatibility.

A useful four-shot laboratory sequence holds the bidder body and time limit constant. Read renderUrl synchronously; schedule it asynchronously without timeout; schedule the preferred renderURL under timeout; then run the upstream deprecated-getter timeout case. Together they isolate compatibility, checkpoint policy, field selection, and the target teardown.

Backport validation should also cover zero timeout, top-level failure, a thrown reporting exception, normal success, reporting timeout, and the optional aggregate path. From the attachment point onward, each exit must prove logger detachment. Debug builds turn a missed path into a destructor assertion.

Three build modes answer different questions. ASAN can expose invalid stack-object access. Debug mode enforces the detach DCHECK. A production-like optimized build confirms that timeout, errors, warnings, and output did not drift with instrumentation. The upstream TEST_F does not contain a literal sanitizer assertion, so each result should keep its own evidentiary role.

The error vector is exact and singular in the async fixture. If a downstream run also produces a JSON conversion failure, script syntax error, network load error, or inspector failure, it may never have reached the target getter sequence. Keeping the fixture's only error as the reporting timeout makes failure clustering useful and prevents an earlier error from masquerading as a security pass.

6.2 Stable shipped .114 and .115 together, so one generic threshold is not enough

Google's July 8 desktop Stable notice lists different platform builds. Windows and macOS received 150.0.7871.114 or 150.0.7871.115. Linux received 150.0.7871.114. The releases rolled out over the following days or weeks.

Google VersionHistory records serving releases for Windows .114 and .115, macOS .114 and .115, and Linux .114. The Linux .115 lookup has no matching release record. Operations should accept the fixed builds in the vendor's platform matrix.

The CNA and NVD normalize the Chrome product range as prior to, or versionEndExcluding, 150.0.7871.115. A single database range cannot express a same-day dual build. Applying it mechanically would label the fixed Linux .114 as vulnerable.

The M150 backport supplies source-side corroboration. Gerrit change 8031201 and its Included-In information associate the correction with both .114 and .115 tags. Platform release provenance therefore outranks numerical string comparison.

Windows or macOS .114 must not be rejected solely because it is numerically below .115. A useful asset record includes vendor, platform, channel, complete version, executable digest, process start time, and the evidence rule used for the decision.

Chromium-derived browsers, CEF, Electron-style runtimes, and custom embedders do not inherit Chrome version semantics automatically. Vendors can carry different branches, backport hashes, and feature configurations. Their strongest evidence is a vendor advisory, fixed source revision, or reviewable equivalent diff joined to a binary artifact.

The same Change-Id was backported to M149, a 7827_199 branch, and M144. M149 Included-In data can point to 149.0.7827.238. Those entries prove source integration into branches, not that ChromeOS, an LTS product, or a third-party runtime released it. Each product still needs publication evidence.

Installation is not execution. A browser farm, kiosk, long-running desktop, or test worker may keep an older process after the package changes. Containers can retain replicas from an old digest. Verification should match the discovery surface: package signature for installation, PID and executable digest for processes, and immutable image digest plus creation time for containers.

Multiple installations complicate inventory further. A system Chrome can be current while portable Chromium, Playwright downloads, user caches, and old test images remain launchable. Scan actual executable paths and hashes instead of relying on one package-manager record.

Rollout produces mixed populations. For days after July 8, one organization could legitimately see .114, .115, and older builds at once. A dashboard must track assets individually. The bulletin publication date is not a fleet-completion date, and an update SLA should measure when each process actually received and started the fixed artifact.

Serving-release data proves that Google distributed a version; it does not prove that every managed endpoint fetched it. The reverse can also occur when an enterprise deploys a signed offline package before a rollout API reflects local timing. Package provenance and live-process digest bridge that difference.

Channel belongs in the decision key. Stable, Extended Stable, Beta, and custom branches can show similar major versions while carrying different release and backport schedules. The desktop matrix in this report directly proves only the covered Stable releases. Other channels need their own vendor mapping.

Rollback monitoring must also recognize a legitimate platform transition from .115 to a fixed .114. A lower number alone is not an alert. Failure means that an asset has left its confirmed fixed set, and platform remains part of that decision key.

Release evidence needs a collection date. VersionHistory and Included-In pages change as tags and rollout state are updated; archive the mapping visible during the July 18 review with its retrieval time. If a later page adds another version, the historical case can still explain why a particular build was accepted at the time.

Hand-drawn regression bench places an envelope, Promise ticket, timeout bell, drawer, five empty bowls, stopped hourglass, sealed error strip, and two green check lamps in sequence.
Figure 6. The regression crosses the Promise and timeout sequence, checks five empty result groups for this fixture, preserves the full timeout outcome, and ends safely under complementary build checks.

7 An investigation must align source, release, and running-process clocks

7.1 The final frame shows where a process fell; the sequence explains why

The source clock records main commit 5a68bc6c... on June 26, M150 backport 4e15f05d... on July 1, and the date a downstream branch adopted equivalent logic. It answers when the correction entered a source line.

The release clock records Google's July 8 platform builds and their rollout. It answers when the vendor offered a fixed package for a channel. It does not prove that an organization installed it immediately.

The process clock records what binary executed at the event time. A package database may show .114 while an old browser process remains alive. Automation can start another Chromium from a cache. This clock often decides whether an incident belongs to the affected source set.

A crash artifact should identify full version, platform, channel, process type, launch time, executable path, digest, and build ID. Auction worklets may run in a service process, so an outer window title or installer version is insufficient. Symbols must match the same build ID; nearby .114 or .115 symbols can mislabel inlined frames.

Useful native landmarks include DeprecatedUrlLazyFiller::HandleDeprecatedUrl, AuctionV8Logger::LogConsoleWarning, ContextRecycler::ResetForReuse, the scope destructor, V8 checkpoint code, and BidderWorklet::V8State::ReportWin. Optimized builds may inline or omit names. The goal is causal adjacency, not a perfect stack containing every label.

Application evidence adds bidder owner, script origin and digest, auction invocation identifier, reporting-timeout configuration, console warning events, and page revision. Content can be represented by hashes when possible; an investigation does not need to copy complete auction profiles or user data to prove that two runs used the same script.

Clock normalization matters. Browser telemetry may use monotonic time, service events UTC, and endpoint updates local time. Preserve the raw value, timezone, conversion rule, and error allowance. Otherwise an analyst can accidentally pair a pre-update crash with a post-update process.

Evidence can be graded as exposure, consistent anomaly, or confirmed incident. A vulnerable process proves exposure. Timeout plus process restart can be a performance fault. Stronger attribution asks whether the process hosted the target worklet, whether the same invocation timed out, and whether symbol or sanitizer evidence joins the lazy getter to teardown.

A single invocation ID can link auction creation, script load, reporting start, timeout decision, service-process failure, and dump creation. Give every node a monotonic time and process identity, then add UTC for cross-host collection. A missing node stays visibly missing instead of being filled from a nearby unrelated event.

A fixed-build comparison should hold bidder digest, page revision, timeout, feature flags, process model, and instrumentation constant. If the same native chain persists on a confirmed fixed artifact, revisit symbol matching, backport completeness, and other lifetime defects. A version label cannot close contradictory evidence.

Without a minidump, crash-loop groups, service exits, worklet-host restarts, and fixed-build comparisons still help. State the sensor gaps. “No evidence found” does not prove the path never ran, and it certainly does not prove code execution.

7.2 There is no dependable traffic signature; version proof and controlled comparison are closer to the truth

The UAF occurs at an internal JavaScript-to-C++ handoff. Network traffic merely delivers a page and bidder code, and TLS often hides even that. Countless source forms can save the object, queue a job, and consume the time limit. Matching the unit-test loop, fixture domain, or test function name would produce a fragile signature.

A reportWin timeout is also common operational telemetry. Script bugs, slow logic, bad input, and test failures can create one. A deprecated-field warning can come from legitimate compatibility code. Their value rises only when joined to a vulnerable build, the correct worklet process, cleanup timing, and native failure.

Fleet detection should therefore begin with version and revision inventory. Use Google's platform matrix for Chrome, vendor evidence for derived runtimes, image and live-process digests for containers, and actual executable scans for portable or automation caches. Vulnerable instances should be updated and rotated before analysts wait for a distinctive crash.

Verify a custom backport in an isolated environment with the upstream regression, negative controls, synthetic bidders, and no production auction data. Record safe timeout completion, synchronous compatibility, clean invocation isolation, source revision, and artifact digest as one traceable result.

Evidence preservation and patching can proceed together. Save existing dumps, logs, hashes, and page revision, then update the process. Keeping a vulnerable browser online to wait for a second crash increases risk without improving the basic source proof.

Feature configuration affects reachability, not code status. An embedder should record whether Protected Audience was compiled, enabled, and hosted in a particular process. Disabling it temporarily can reduce exposure, but the binary still needs a fixed source revision before it is marked repaired.

Owned bidder code should migrate from renderUrl to renderURL. That reduces warning noise and removes one deprecated callback from normal traffic. Third-party scripts can still use the alias, so script governance and engine patching remain separate workstreams.

During a short distribution gap, managed environments can use vendor-supported feature controls or restrict untrusted bidder sources to reduce reachability. Such measures need an owner, expiry, and rollback plan. They remain temporary exposure controls because they do not change the vulnerable instructions in the binary.

Group crashes first by build ID, process type, top native module, and time window, then search within each cluster for getter and checkpoint evidence. Stable grouping fields keep every V8 crash from collapsing into one uninvestigable bucket when symbols are incomplete.

An explicit sanitizer finding of stack-use-after-scope still needs attribution. Confirm that the reported variable is deprecated_render_url or a logger-related object; the same error class can arise elsewhere in worklet code. The variable, source location, and regression timing together decide whether the event belongs to this defect.

Hand-drawn investigation desk connects source commit, platform release, and running-process clocks to a browser binary, crash folder, worklet envelope, and restart lamp.
Figure 7. Source containing the patch, a vendor shipping the patch, and an event running the patched process are three different findings. All three clocks must land on the same asset.

8 From fixed commit to live process, the Promise ticket finally reaches a safe helper

8.1 The repair ledger must join source, build, package, process, test, and rollback

The ledger begins at fixed source. Pin the full main commit, Gerrit review, M150 backport, six changed files, and regression with stable URLs or hashes. On the same page, mark filler construction before the scope, the logger's null-attached-null state, the getter guard, destructor DCHECK, reset-before-checkpoint, and seller parameter cleanup. “Includes the upstream fix” then becomes a set of behaviors that can be traced into the downstream diff.

Once source becomes a binary, continue the chain with its revision, compiler and flags, feature configuration, symbols, and artifact digest. If a vendor repackages it, the signed package must lead back to that build. Chrome adds platform and channel; containers add immutable image digests; embedders add the mapping from the outer release to its Chromium revision. Testing one artifact and shipping another breaks the chain here.

A landed package is only the midpoint. After rotation, collect the browser and worklet host PID, start time, executable path, complete version, and digest; clear orphan workers, portable copies, and automation caches. An updater can replace the file on disk while an old process still maps the previous image, just as a container tag can move while old replicas keep running. Preserve the event-time process or instance digest instead of rehashing a later file at the same path.

Regression evidence must identify the same artifact. Preserve ASAN, debug, and production-like results for the async timeout case, synchronous legacy warning, preferred-field no-warning control, stage failures, and multi-invocation isolation. A defined observation window then joins process revisions, reporting-timeout baselines, related crash groups, and rollback events. Timeouts need not fall to zero; old revisions and matching native faults should.

An equivalent backport may rename functions, but it cannot discard four properties: the callback owner covers the last checkpoint, the short-lived logger disconnects before leaving, native links reset before the drain, and the causal test preserves timeout semantics. Behavioral proof accepts a safe refactor without letting a textually similar but misplaced declaration pass.

8.2 Close the room by detaching the logger, folding native links, and then releasing the queued ticket

An operational response can follow the same order as the objects in the source:

  1. Deploy fixed Chrome builds by platform. Accept 150.0.7871.114 or .115 for Windows and macOS and .114 for Linux from Google's July 8 bulletin. Downstream products need their own vendor or revision proof. Record channel, package digest, and source of the decision.
  2. Rotate every persistent process. Restart desktop sessions, browser farms, kiosks, ad-test workers, container replicas, and embedded services. Read the live version, start time, and executable digest until old PIDs are gone.
  3. Inventory the reporting path. Find products that enable Protected Audience, execute third-party bidder scripts, or run auction worklets frequently. Enumerate portable executables, browser download caches, and stale test images separately.
  4. Verify the complete source repair. Check filler declaration order, logger attachment and detachment, getter guard, recycler reset order, seller cleanup, and six-file provenance. Walk every exit reachable after attachment and retain the final diff.
  5. Run causal regressions. Execute the Promise-timeout case, synchronous deprecation control, preferred-field control, stage failures, and multi-invocation isolation. Save raw logs, build digest, time-limit configuration, and typed output.
  6. Preserve and review crashes. Join vulnerable-process evidence, reporting timeout, V8 checkpoint, lazy getter, and logger or filler frames. Keep matching build-ID symbols and distinguish exposure, consistent anomaly, and confirmed incident.
  7. Migrate owned legacy scripts. Change controlled bidder code to renderURL and use the inspector tests to monitor third-party compatibility. This reduces obsolete callback traffic but does not replace the browser security update.
  8. Lock the safe rollback point. Prevent schedulers, caches, and emergency images from launching revisions without the patch. Allow only immutable, attested safe artifacts and complete the two-way evidence sign-off after the observation window.

Return to the reporting desk. The Promise ticket can still arrive after the timeout bell; Chromium does not pretend the queue vanished. The filler remains until scope cleanup completes, the logger cable is unplugged before its owner leaves, and recycler-owned connections fold away before the tray turns.

When PerformMicrotaskCheckpoint() finally resumes the getter, callback data reaches a live filler. The URL can still be converted. The logger member is null, so no call enters the finished per-call logger. Only after the checkpoint does reverse destruction remove the filler.

Each patch element answers a different acceptance question. Declaration order protects the callback owner. Attach and detach constrain the borrowed logger. The getter guard defines cleanup behavior. Reset ordering prepares other context-visible state. The upstream regression directly locks the filler lifetime, logger detach and guard, and timeout result. It does not separately assert recycler-owned reset ordering or seller preventive cleanup; those remain fixed-diff and targeted-control acceptance items.

The public chronology now closes: researcher report on June 24, main fix on June 26, M150 backport on July 1, and platform Stable delivery on July 8. Those dates belong to discovery, source, branch, and release clocks. None alone proves that a particular machine is safe.

If V8 later moves its terminating checkpoint, re-establish that this fixture still resumes the deprecated getter during teardown, adding trace points, breakpoints, or a targeted assertion when needed. If the reaction moves to an ordinary checkpoint, an unchanged timeout result can pass without exercising the original cleanup sequence.

A minimal long-term case file can retain the Stable bulletin, fixed diff, build digest, running version, raw regression result, and observation conclusion. It supports later audit without preserving complete bidder scripts, user data, or auction profiles.

The claim ticket is simply correctly deferred work. V8 still owns the blue envelope, and the old drawer still serves compatible scripts. What disappeared is the accidental connection from that ticket to expired stack storage. The story starts with a timeout and ends only when the running process and its evidence agree.

Hand-drawn closure bench links fixed source, sealed build, platform package, running process, regression lamp, and rollback drawer until a Promise ticket reaches a helper that is still present.
Figure 8. The six stations are source, artifact, package, running process, regression, and rollback proof, not a count of patch files. The same ticket reaches the end with its helper alive, logger disconnected, and old links reset.

Research record

9Evidence, objects, and sources

The material below preserves the identifiers and references used in this report.

9.1Research objects

Products, actors, techniques, affected objects, and control points discussed in the report.

VulnerabilityCVE-2026-15133

Chromium InterestGroups reportWin use-after-free

Chromium bug527406824

reportWin deprecated URL handler lifetime issue

Main fix5a68bc6c97d97312ffa0f9ac04e2288f1cdadd13

main@{#1653488}, review 8006698

M150 backport4e15f05d550a3c0f7268a11e85427b0c6330e525

branch-heads/7871 backport

Key testBidderWorkletTest.ReportWinBrowserSignalRenderUrlDeprecationAsync

Promise, deprecated getter, and timeout regression

Trigger shapesavedBrowserSignals.renderUrl + reportWin timeout

Delayed checkpoint reenters getter after termination

9.2Event chronology

  1. Researcher reported the issue

    Jihyeon Jeong reported the InterestGroups use-after-free to Google.

  2. Chromium main fix landed

    Commit 5a68bc6c entered main as main@{#1653488}.

  3. M150 backport landed

    Commit 4e15f05d entered branch-heads/7871.

  4. Chrome Stable bulletin published

    Google shipped the fixed desktop builds and disclosed CVE-2026-15133.

  5. SOSEC completed the source reconstruction

    SOSEC pinned the six-file diff, regression assertions, platform releases, and runtime proof chain.

9.3Sources and material

  1. Chrome desktop Stable security update, July 8, 2026https://chromereleases.googleblog.com/2026/07/stable-channel-update-for-desktop_01162222768.html
  2. CVE-2026-15133 recordhttps://www.cve.org/CVERecord?id=CVE-2026-15133
  3. Official CVE Program JSONhttps://cveawg.mitre.org/api/cve/CVE-2026-15133
  4. NVD entry for CVE-2026-15133https://nvd.nist.gov/vuln/detail/CVE-2026-15133
  5. Chromium main fix 5a68bc6chttps://chromium.googlesource.com/chromium/src/+/5a68bc6c97d97312ffa0f9ac04e2288f1cdadd13
  6. Chromium review 8006698https://chromium-review.googlesource.com/c/chromium/src/+/8006698
  7. Fixed bidder_worklet.cchttps://chromium.googlesource.com/chromium/src/+/5a68bc6c97d97312ffa0f9ac04e2288f1cdadd13/content/services/auction_worklet/bidder_worklet.cc
  8. Asynchronous reportWin regressionhttps://chromium.googlesource.com/chromium/src/+/5a68bc6c97d97312ffa0f9ac04e2288f1cdadd13/content/services/auction_worklet/bidder_worklet_unittest.cc#8715
  9. ContextRecycler reset orderhttps://chromium.googlesource.com/chromium/src/+/5a68bc6c97d97312ffa0f9ac04e2288f1cdadd13/content/services/auction_worklet/context_recycler.cc#185
  10. LazyFiller context lifetime contracthttps://chromium.googlesource.com/chromium/src/+/5a68bc6c97d97312ffa0f9ac04e2288f1cdadd13/content/services/auction_worklet/lazy_filler.h
  11. DeprecatedUrlLazyFiller interface contracthttps://chromium.googlesource.com/chromium/src/+/5a68bc6c97d97312ffa0f9ac04e2288f1cdadd13/content/services/auction_worklet/deprecated_url_lazy_filler.h
  12. DeprecatedUrlLazyFiller implementationhttps://chromium.googlesource.com/chromium/src/+/5a68bc6c97d97312ffa0f9ac04e2288f1cdadd13/content/services/auction_worklet/deprecated_url_lazy_filler.cc
  13. Seller worklet preventative cleanuphttps://chromium.googlesource.com/chromium/src/+/5a68bc6c97d97312ffa0f9ac04e2288f1cdadd13/content/services/auction_worklet/seller_worklet.cc
  14. M150 backport 4e15f05dhttps://chromium.googlesource.com/chromium/src/+/4e15f05d550a3c0f7268a11e85427b0c6330e525
  15. M150 backport Included-In tagshttps://chromium-review.googlesource.com/changes/8031201/in
  16. Windows Stable 150.0.7871.114 release recordhttps://versionhistory.googleapis.com/v1/chrome/platforms/win/channels/stable/versions/150.0.7871.114/releases
  17. Windows Stable 150.0.7871.115 release recordhttps://versionhistory.googleapis.com/v1/chrome/platforms/win/channels/stable/versions/150.0.7871.115/releases
  18. macOS Stable 150.0.7871.114 release recordhttps://versionhistory.googleapis.com/v1/chrome/platforms/mac/channels/stable/versions/150.0.7871.114/releases
  19. macOS Stable 150.0.7871.115 release recordhttps://versionhistory.googleapis.com/v1/chrome/platforms/mac/channels/stable/versions/150.0.7871.115/releases
  20. Linux Stable 150.0.7871.114 release recordhttps://versionhistory.googleapis.com/v1/chrome/platforms/linux/channels/stable/versions/150.0.7871.114/releases
  21. Protected Audience auction reporting guidehttps://privacysandbox.google.com/private-advertising/protected-audience-api/ad-auction-reporting
  22. Protected Audience / FLEDGE explainerhttps://github.com/WICG/turtledove/blob/main/FLEDGE.md