Vulnerability Survey
V8 Bugs in the Agentic Era
Vulnerability Survey
V8 Bugs in the Agentic Era
By the end of September 2026, Chrome had fixed 131 V8 CVEs this year. That is more than 2024 and 2025 together (60 and 48). Chrome as a whole jumped even more: 2,908 CVEs so far in 2026, compared with 190 in all of 2025.
In Part 1 of our AI series, we described how language models went from helping with code review to working as research agents that read code, come up with ideas, and test them. This post looks at the bugs themselves. If agents now help find V8 bugs, the public record should show it: in how many bugs get fixed, in what kinds of bugs they are, and in where they are in the engine.
We looked at every V8 CVE Chrome has published since 2009, the commit that fixed each one, and the bugs known to have been exploited in the wild.
TLDR: far more bugs are being found, in more places and of more kinds. The bugs that attackers actually use still look the way they always have.
Data and method
We started from two public lists: v8-cves and chrome-cves. Both come from the Chrome Releases blog. For each CVE they record the description, Chromium’s severity rating, whether it was exploited in the wild, the issue number, the fix commits, and the Chrome release with the fix. The V8 list has 544 CVEs and the Chrome list has 6,709.
We added two things:
- Bug class. Chrome describes each CVE in a few words, such as “Type confusion in V8” or “Use after free in V8”. We sorted these into type confusion, out-of-bounds access, use-after-free, race condition, integer overflow, and “inappropriate implementation”, which is Chromium’s name for logic bugs.
- Area. For each CVE we downloaded the V8 fix commit and noted which source folders it changed, ignoring tests.
src/compiler/counts as TurboFan,src/compiler/turboshaft/as Turboshaft,src/maglev/as Maglev, and so on. This worked for 491 of the 493 fix commits.
A few caveats:
- Chrome describes each bug in a few words, so some bugs could fit more than one class.
- We look at where a bug was fixed, which is not always where it started.
- A bug is counted in the year its fix shipped, not the year it was found.
- 2026 isn’t over yet, so its numbers will only go up.
Figure 1 shows every year since 2009.

Figure 1. V8 CVEs per year, with bugs exploited in the wild in orange. 2026 runs through September 22.
For about ten years, V8 had 20 to 40 CVEs a year. 2024 had 60. 2026 already has more than twice that.
The 2026 jump
The increase did not happen all at once. Figure 2 shows each 2026 V8 CVE on the day its fix shipped, in the style of v8bugs.fyi.

Figure 2. V8 CVEs per Chrome release day in 2026.
January and February look like any recent year, with 4 and 3 CVEs. From March the number goes up every month, reaches 33 in June, and stays around 20 a month after that. Figure 3 shows the same months for all of Chrome.

Figure 3. CVEs per month in 2026, all of Chrome on the left and V8 on the right.
Chrome follows the same pattern on a much bigger scale, with 1,018 CVEs in June alone. This lines up with the AI research agents we covered in Part 1 becoming publicly available.
Google has said more about this. In July, the Chrome security team wrote that Chrome 149 and 150 fixed 1,072 security bugs, more than the 23 releases before them combined, and described using AI agents built on Gemini both to find bugs and to fix them. The same month, Google DeepMind reported that its security-tuned model, Gemini 3.5 Flash Cyber, found 55 confirmed issues in V8 in a fixed test.
None of this tells us who found each individual bug, so we cannot prove that agents caused the jump. It does show that the jump is real, that it covers all of Chrome, and that the V8 bugs being fixed have changed. The rest of this post looks at what kinds of bugs they are and where in the engine they are.
What kinds of bugs
Figure 4 splits each year by bug class.

Figure 4. V8 CVEs by bug class and year. Numbers inside the bars are counts.
From 2021 to 2023, most V8 CVEs were type confusions: 16 of 19 in 2023. 2026 is different. Type confusion is still the biggest class with 32, but use-after-free went from 3 in 2025 to 30. There are also 21 logic bugs (“inappropriate implementation”), 10 integer overflows, and 9 race conditions.
Severity changed too (Figure 5). Every V8 CVE in 2023 was rated High, and about 85% were in 2024 and 2025. In 2026, 81 of 131 are High, 30 are Medium, and 20 are Low.

Figure 5. Chromium severity ratings for V8 CVEs by year.
More bugs, more kinds of bugs, and more lower-severity ones. This is what you would expect if people were searching more widely, not only more deeply. The next sections go through each class, using real 2026 fixes as examples.
Type confusion
V8 keeps a description of each object’s layout, called its map, and tracks what kind of values each array holds. The optimizing compilers generate code for the layouts they have seen, and add checks to make sure those layouts still hold. A type confusion happens when a check is missing or out of date, and the code treats an object as something it is not.
The first example was exploited in the wild. It is in how the compiler inlines Array.prototype.sort [9]. When the arrays passed in had different element kinds, the inlined sort used a combined kind that no single array had to match. It copied the elements, called the user’s compare function, and then wrote the copy back. If the compare function changed the array’s element kind in the meantime, the write-back used the wrong layout. The fix stops inlining sort in this case.
The second example is smaller. The Array.prototype.flat builtin converted a length to a small integer without checking that it was one [10]:
// src/builtins/array-flat.tq
- // The source is a FastJSArray, thus its length must be a Smi.
- dcheck(Is<Smi>(sourceLength));
- const smiSourceLength = UnsafeCast<Smi>(sourceLength);
+ const smiSourceLength = Cast<Smi>(sourceLength)
+ otherwise goto Bailout(targetIndex, smiSourceIndex);
The old code assumed the length was a small integer (Smi) and only checked it in debug builds. The fix checks in every build and takes the slow path when the check fails.
Both bugs come down to code trusting a type it never checked.
Out-of-bounds access
Bounds and range checks cost time, so the compilers try to prove they are not needed and remove them. Out-of-bounds bugs often come from a proof that is slightly wrong. One 2026 fix keeps a safe-integer check that TurboFan could drop when turning a number conversion into a 32-bit operation [11]. Another is in instruction selection, the step that turns the compiler’s graph into machine instructions [12]:
// src/compiler/backend/x64/instruction-selector-x64.cc
if (imm == 0) {
- if (this->Get(left).outputs_rep()[0] ==
- RegisterRepresentation::Word32()) {
+ if (ZeroExtendsWord32ToWord64(left)) {
// {EmitIdentity} reuses the virtual register of the first input
- // for the output. This is exactly what we want here.
+ // for the output. This is only safe if it is known to zero-extend
+ // (as the int32 subtraction advertises itself as zero-extending.)
EmitIdentity(node);
For x - 0, the compiler reused x as the result whenever x was a 32-bit value. But the result promises that its upper 32 bits are zero, and not every 32-bit value keeps that promise. The fix only reuses x when it does.
Neither bug is a missing if in C++. Both are small mistakes in how the compiler reasons about values.
Use-after-free
A use-after-free is when code keeps using memory after the object in it has been freed. In V8 this usually means code still holds a raw pointer to an object the garbage collector has moved or freed, or uses state left behind after deoptimization.
A 2026 fix shows the classic version [13]. When V8 works with two strings at once, for example to search for one inside the other, it first takes a direct pointer to each string’s characters. For an external string, one whose characters live outside V8’s heap, that pointer does not keep the string alive. Preparing the second string could allocate memory, the allocation could start a garbage collection, and the collection could free the first string’s characters while V8 still pointed at them:
// src/objects/string.tq
macro TwoStringsToSlices<Result: type, Functor: type>(
s1: String, s2: String, f: Functor): Result {
+ // Flatten both strings before taking either slice. A slice into an external
+ // string is a raw pointer that doesn't keep the string alive, so flattening
+ // s2 while holding s1's slice could collect s1 and free its resource.
+ const s1 = Flatten(s1);
+ const s2 = Flatten(s2);
try {
StringToSlice(s1) otherwise FirstOneByte, FirstTwoByte;
The comment is V8’s own. The fix does all the work that can allocate memory before it takes any raw pointers.
Race conditions
V8 compiles code, collects garbage, and more and more often resizes buffers on background threads. A race condition happens when the main thread and a background thread disagree about the same object. One 2026 fix handles a typed array that a background thread grows while Object.values is reading it [14]:
// src/objects/elements.cc
size_t length = AccessorClass::GetCapacityImpl(*object, *elements);
+ // The TypedArray might have been grown by a background thread. Handle it
+ // gracefully.
+ if (length > max_nof_items) {
+ length = max_nof_items;
+ }
for (size_t index = 0; index < length; ++index) {
The result array was sized before this loop. If another thread grew the typed array in between, the loop could run past the end of the result. The fix caps the loop at the size the result was made for.
Another makes Maglev create a new number object for each deoptimization instead of sharing one [15].
Integer overflow
Integer overflows are usually size calculations: a length multiplied, a separator repeated, or a count that gets too big. Array.prototype.join now checks early for an overflow in the separator length [16]:
// src/builtins/array-join.tq
- // Detect integer overflow
- // TODO(turbofan): Replace with overflow-checked multiplication.
- if (sepsLen / separatorLength != nofSeparatorsInt) deferred {
- ThrowInvalidStringLength(context);
- }
...
+ if (separatorLength > 1 && len > 1) {
+ ...
+ const res = TryInt32Mul(lenInt - 1, sepLenInt) otherwise IfOverflow;
+ if (res > Convert<int32>(kStringMaxLength)) goto IfOverflow;
+ } label IfOverflow {
+ ThrowInvalidStringLength(context);
+ }
+ }
The old code multiplied first and checked for overflow afterwards. The fix does an overflow-checked multiply at the start, before any work is done.
Maglev also now stops compiling when an input node gets too big [17].
Logic bugs
“Inappropriate implementation” covers bugs where V8 does something the language does not allow, without a memory bug in the fix itself. One 2026 example is in the parser: V8 skipped a temporal dead zone check for hoisted functions inside sloppy-mode eval [18]. The fix moves six lines:
// src/ast/scopes.cc
if (scope->outer_scope_ == outer_scope_end) break;
+ if (scope->is_hoisted_in_context()) {
+ *access_position = scope->outer_scope()->start_position();
+ } else if (scope->is_eval_scope()) {
+ *access_position = scope->AsDeclarationScope()->eval_position();
+ }
+
if (V8_UNLIKELY(scope->is_dynamic_scope())) {
...
- if (scope->is_hoisted_in_context()) {
- *access_position = scope->outer_scope()->start_position();
- } else if (scope->is_eval_scope()) {
- *access_position = scope->AsDeclarationScope()->eval_position();
- }
V8 decides whether a variable needs a temporal dead zone check by comparing where it is used with where it is declared. The old code updated that position too late for code inside eval, so the check could be skipped.
Another logic bug fix changed how for-of loops return values in the interpreter and all three compilers [19]. To find bugs like these you need to know the JavaScript specification, not only the engine.
Where the bugs are
Figure 6 compares where fixes went in 2019–2025 with where they went in 2026.

Figure 6. Share of V8 CVEs whose fix touched each area. A fix can touch more than one area, so the shares add up to more than 100%.
Three things changed.
TurboFan is no longer on top. From 2019 to 2025, 27% of V8 fixes touched TurboFan, far more than any other area. In 2026 that dropped to 15%. The number of TurboFan CVEs still went up, from about 8 a year to 18, but other areas went up faster.
The newer compilers caught up. Maglev, V8’s mid-tier optimizing compiler, went from 7% of fixes to 18% and is now the most common area. Turboshaft, the newer compiler backend used by TurboFan and WebAssembly, went from 5% to 11%. Both are newer code that has been fuzzed for fewer years.
The debugger showed up. The debugger and inspector went from 4% to 16%. This code has always been part of V8, but security research rarely looked at it, partly because web pages usually cannot reach it.
In 2026, 19 CVEs had fixes in debugger or inspector code. 11 of them are use-after-frees, and only 5 are rated High, which fits code that usually needs a debugger attached.
A closer look at Maglev
Maglev is V8’s mid-tier optimizing compiler. It shipped in Chrome 117 in 2023 and sits between Sparkplug, which compiles quickly but simply, and TurboFan, which compiles slowly but makes the fastest code. Its job is to make good-enough code quickly.
In 2026, 22 V8 CVEs had fixes in Maglev. 19 of them are rated High and 12 are type confusions, and one of them, the sort bug above, was exploited in the wild. Unlike the debugger bugs, these are the kind attackers look for.
Several are the same mistake in one place. To make code faster, Maglev skips checks it can prove are not needed. One of these checks makes sure a value is a small integer, which V8 calls a Smi. At least five 2026 fixes tighten when Maglev may skip it, and two of them change the same function [20][21]:
// src/maglev/maglev-graph-builder.cc, in BuildCheckSmi()
- // For constants, we may be able to skip the runtime check.
- if (std::optional<int32_t> constant_value = TryGetInt32Constant(object)) {
- if (Smi::IsValid(constant_value.value())) return object;
+ // For non-tagged constants, we may be able to skip the runtime check ...
+ if (object->value_representation() != ValueRepresentation::kTagged) {
+ if (std::optional<int32_t> constant_value = TryGetInt32Constant(object)) {
+ if (Smi::IsValid(constant_value.value())) return object;
+ }
}
Maglev skipped the check when it knew a value was equal to a small number. But a JavaScript value can equal a small number and still be stored as a full number object instead of a Smi. The fix only skips the check for raw machine numbers, where the value is all that matters.
What still gets exploited
Bugs used in real attacks look different. Since 2016, Chrome has published 75 CVEs exploited in the wild, and 41 of them were in V8. Figure 7 compares the bug classes of all V8 CVEs in that period with the exploited ones.

Figure 7. Bug class share of all V8 CVEs since 2016 (grey) and of those exploited in the wild (orange).
Type confusions are 31% of all V8 CVEs but 46% of the exploited ones. Use-after-frees go the other way: 14% of all V8 CVEs, but only 5% of the exploited ones.
Figure 8 shows the same gap by area.

Figure 8. Share of V8 CVEs since 2016 whose fix touched each area, for all V8 CVEs (grey) and for those exploited in the wild (orange).
TurboFan appears in 23% of all V8 fixes but in 50% of the exploited ones. WebAssembly goes the other way, 16% against 6%, and none of the exploited bugs were fixed in the debugger.
In 2026, 4 of the 131 V8 CVEs were exploited in the wild: one type confusion (the sort bug above [9]), two out-of-bounds bugs, and one logic bug. That is too few to show a trend on its own.
A note on the V8 sandbox
Not every V8 security fix gets a CVE. The V8 sandbox is meant to keep memory corruption contained inside the engine, and it gets its own hardening fixes, which usually have no CVE. v8bugs.fyi tracks these separately and counts 128 so far in 2026. The fix titles we looked at cover the same kinds of problems as above: races on shared tables, values read twice from memory an attacker can change, and state passed between trusted and untrusted objects.
What this means for researchers
More V8 bugs are being found, in more parts of the engine, and Google says AI now helps both find and fix them. That is good for users.
But the bugs attackers use have barely changed. Type confusions are still the most common kind, often in the optimizing compilers. And since 2023, the new findings have looked less like them each year: type confusions fell from 84% of V8 CVEs in 2023 to 24% in 2026, while they make up 46% of the V8 bugs exploited in the wild.
That gap is where we think research time is best spent. Finding these bugs takes deep knowledge of one part of the engine and a way to aim a search at it, which is what we built SeekZero for.
References
- Tashita, “AI Browser Vulnerability Research Part 1: The Rise of AI-Driven Browser Vulnerability Research.” https://tashita.net/ai-browser-vulnerability-research-part-1/
- v8-cves. https://github.com/str8outtaheap/v8-cves
- chrome-cves. https://github.com/str8outtaheap/chrome-cves
- Chrome Releases. https://chromereleases.googleblog.com/
- V8 blog, “Maglev – V8’s Fastest Optimizing JIT,” December 5, 2023. https://v8.dev/blog/maglev
- v8bugs.fyi. https://v8bugs.fyi/
- Chrome Security Team, “Stronger with every update: How we’re making Chrome and the web safer in the AI Era,” July 30, 2026. https://blog.google/security/chrome-stronger-with-every-update/
- Google DeepMind, “Introducing Gemini 3.5 Flash Cyber,” July 21, 2026. https://deepmind.google/blog/introducing-gemini-3-5-flash-cyber/
- CVE-2026-85046 fix: “[compiler] Don’t inline Array.prototype.sort on mixed elements kinds.” https://chromium.googlesource.com/v8/v8/+/e0562d87ad9c17042b581582c99237d798572e67
- CVE-2026-87564 fix: “[builtins] Fix Array.prototype.flat.” https://chromium.googlesource.com/v8/v8/+/50a6c0c69c1384720d9c3599dadfa73e55b865d7
- CVE-2026-15903 fix: “[turbofan] Keep safe-integer check in ToNumber Word32 lowering.” https://chromium.googlesource.com/v8/v8/+/a7835b8c294c97d6d6cbfc22582de40cfcbf0c19
- CVE-2026-95304 fix: “[compiler] ISel: Fix VisitInt32Sub for 32-bit lhs.” https://chromium.googlesource.com/v8/v8/+/057dcc408de1583cf68c4034bbba89d0f78a88a4
- CVE-2026-19556 fix: “[string] Fix UAF in TwoStringsToSlices with external strings.” https://chromium.googlesource.com/v8/v8/+/9062318d79629d4084053747fec654a1a49dc690
- CVE-2026-5893 fix: “[RAB/GSAB] Object.values: Handle a TA grown by a background thread gracefully.” https://chromium.googlesource.com/v8/v8/+/02cd73dfc58dbe73054382c85f9f126af9ce597e
- CVE-2026-85045 fix: “[maglev] Materialize a fresh HeapNumber per deoptimization.” https://chromium.googlesource.com/v8/v8/+/7d5eb1df2a721c8fe3aa03b999464c4b86242080
- CVE-2026-14430 fix: “Check early for separator overflow in Array.prototype.join().” https://chromium.googlesource.com/v8/v8/+/8c3c9f464a45a8809581ff45382bb46d01eb9171
- CVE-2025-10892 fix: “[maglev] Abort compilation if input node is too big.” https://chromium.googlesource.com/v8/v8/+/618d1a029005753ca036166b9b0684c7bc333077
- CVE-2026-10935 fix: “[ast] Fix hole check elision for hoisted functions in sloppy eval.” https://chromium.googlesource.com/v8/v8/+/ad07ea9d88fd5c2a529d35fb4ebec046f5404b99
- CVE-2026-14383 fix: “[for-of] Use a single return value.” https://chromium.googlesource.com/v8/v8/+/02a725e2a0762f39266fdac2ba2869c71af37ed2
- CVE-2026-7337 fix: “[maglev] Restrict BuildCheckSmi constant elision to non-tagged inputs.” https://chromium.googlesource.com/v8/v8/+/cc9a97f192319ed218c191fa20cbecd1bf51cb71
- CVE-2026-5865 fix: “[maglev] Avoid eliding Smi checks too aggressively.” https://chromium.googlesource.com/v8/v8/+/fac60cc3fdf84591594354fb3629a2fcb376ff23
Keep reading
Related research
- Where Pixels Go Wrong: Chrome’s Graphics Stack: 506 Chrome graphics CVEs in 2026.
- CVE-2026-6307 (Part 1): Turbofan JS-to-Wasm Deopt Type Confusion: root cause and trigger.
- AI Browser Vulnerability Research, Part 1: the rise of AI-driven browser vulnerability research.
Learn more about SeekZero, our browser vulnerability research framework, or browse all posts on the Tashita research blog.
Vulnerability Survey
V8 Bugs in the Agentic Era
Vulnerability Survey
V8 Bugs in the Agentic Era
By the end of September 2026, Chrome had fixed 131 V8 CVEs this year. That is more than 2024 and 2025 together (60 and 48). Chrome as a whole jumped even more: 2,908 CVEs so far in 2026, compared with 190 in all of 2025.
In Part 1 of our AI series, we described how language models went from helping with code review to working as research agents that read code, come up with ideas, and test them. This post looks at the bugs themselves. If agents now help find V8 bugs, the public record should show it: in how many bugs get fixed, in what kinds of bugs they are, and in where they are in the engine.
We looked at every V8 CVE Chrome has published since 2009, the commit that fixed each one, and the bugs known to have been exploited in the wild.
TLDR: far more bugs are being found, in more places and of more kinds. The bugs that attackers actually use still look the way they always have.
Data and method
We started from two public lists: v8-cves and chrome-cves. Both come from the Chrome Releases blog. For each CVE they record the description, Chromium’s severity rating, whether it was exploited in the wild, the issue number, the fix commits, and the Chrome release with the fix. The V8 list has 544 CVEs and the Chrome list has 6,709.
We added two things:
- Bug class. Chrome describes each CVE in a few words, such as “Type confusion in V8” or “Use after free in V8”. We sorted these into type confusion, out-of-bounds access, use-after-free, race condition, integer overflow, and “inappropriate implementation”, which is Chromium’s name for logic bugs.
- Area. For each CVE we downloaded the V8 fix commit and noted which source folders it changed, ignoring tests.
src/compiler/counts as TurboFan,src/compiler/turboshaft/as Turboshaft,src/maglev/as Maglev, and so on. This worked for 491 of the 493 fix commits.
A few caveats:
- Chrome describes each bug in a few words, so some bugs could fit more than one class.
- We look at where a bug was fixed, which is not always where it started.
- A bug is counted in the year its fix shipped, not the year it was found.
- 2026 isn’t over yet, so its numbers will only go up.
Figure 1 shows every year since 2009.

Figure 1. V8 CVEs per year, with bugs exploited in the wild in orange. 2026 runs through September 22.
For about ten years, V8 had 20 to 40 CVEs a year. 2024 had 60. 2026 already has more than twice that.
The 2026 jump
The increase did not happen all at once. Figure 2 shows each 2026 V8 CVE on the day its fix shipped, in the style of v8bugs.fyi.

Figure 2. V8 CVEs per Chrome release day in 2026.
January and February look like any recent year, with 4 and 3 CVEs. From March the number goes up every month, reaches 33 in June, and stays around 20 a month after that. Figure 3 shows the same months for all of Chrome.

Figure 3. CVEs per month in 2026, all of Chrome on the left and V8 on the right.
Chrome follows the same pattern on a much bigger scale, with 1,018 CVEs in June alone. This lines up with the AI research agents we covered in Part 1 becoming publicly available.
Google has said more about this. In July, the Chrome security team wrote that Chrome 149 and 150 fixed 1,072 security bugs, more than the 23 releases before them combined, and described using AI agents built on Gemini both to find bugs and to fix them. The same month, Google DeepMind reported that its security-tuned model, Gemini 3.5 Flash Cyber, found 55 confirmed issues in V8 in a fixed test.
None of this tells us who found each individual bug, so we cannot prove that agents caused the jump. It does show that the jump is real, that it covers all of Chrome, and that the V8 bugs being fixed have changed. The rest of this post looks at what kinds of bugs they are and where in the engine they are.
What kinds of bugs
Figure 4 splits each year by bug class.

Figure 4. V8 CVEs by bug class and year. Numbers inside the bars are counts.
From 2021 to 2023, most V8 CVEs were type confusions: 16 of 19 in 2023. 2026 is different. Type confusion is still the biggest class with 32, but use-after-free went from 3 in 2025 to 30. There are also 21 logic bugs (“inappropriate implementation”), 10 integer overflows, and 9 race conditions.
Severity changed too (Figure 5). Every V8 CVE in 2023 was rated High, and about 85% were in 2024 and 2025. In 2026, 81 of 131 are High, 30 are Medium, and 20 are Low.

Figure 5. Chromium severity ratings for V8 CVEs by year.
More bugs, more kinds of bugs, and more lower-severity ones. This is what you would expect if people were searching more widely, not only more deeply. The next sections go through each class, using real 2026 fixes as examples.
Type confusion
V8 keeps a description of each object’s layout, called its map, and tracks what kind of values each array holds. The optimizing compilers generate code for the layouts they have seen, and add checks to make sure those layouts still hold. A type confusion happens when a check is missing or out of date, and the code treats an object as something it is not.
The first example was exploited in the wild. It is in how the compiler inlines Array.prototype.sort [9]. When the arrays passed in had different element kinds, the inlined sort used a combined kind that no single array had to match. It copied the elements, called the user’s compare function, and then wrote the copy back. If the compare function changed the array’s element kind in the meantime, the write-back used the wrong layout. The fix stops inlining sort in this case.
The second example is smaller. The Array.prototype.flat builtin converted a length to a small integer without checking that it was one [10]:
// src/builtins/array-flat.tq
- // The source is a FastJSArray, thus its length must be a Smi.
- dcheck(Is<Smi>(sourceLength));
- const smiSourceLength = UnsafeCast<Smi>(sourceLength);
+ const smiSourceLength = Cast<Smi>(sourceLength)
+ otherwise goto Bailout(targetIndex, smiSourceIndex);
The old code assumed the length was a small integer (Smi) and only checked it in debug builds. The fix checks in every build and takes the slow path when the check fails.
Both bugs come down to code trusting a type it never checked.
Out-of-bounds access
Bounds and range checks cost time, so the compilers try to prove they are not needed and remove them. Out-of-bounds bugs often come from a proof that is slightly wrong. One 2026 fix keeps a safe-integer check that TurboFan could drop when turning a number conversion into a 32-bit operation [11]. Another is in instruction selection, the step that turns the compiler’s graph into machine instructions [12]:
// src/compiler/backend/x64/instruction-selector-x64.cc
if (imm == 0) {
- if (this->Get(left).outputs_rep()[0] ==
- RegisterRepresentation::Word32()) {
+ if (ZeroExtendsWord32ToWord64(left)) {
// {EmitIdentity} reuses the virtual register of the first input
- // for the output. This is exactly what we want here.
+ // for the output. This is only safe if it is known to zero-extend
+ // (as the int32 subtraction advertises itself as zero-extending.)
EmitIdentity(node);
For x - 0, the compiler reused x as the result whenever x was a 32-bit value. But the result promises that its upper 32 bits are zero, and not every 32-bit value keeps that promise. The fix only reuses x when it does.
Neither bug is a missing if in C++. Both are small mistakes in how the compiler reasons about values.
Use-after-free
A use-after-free is when code keeps using memory after the object in it has been freed. In V8 this usually means code still holds a raw pointer to an object the garbage collector has moved or freed, or uses state left behind after deoptimization.
A 2026 fix shows the classic version [13]. When V8 works with two strings at once, for example to search for one inside the other, it first takes a direct pointer to each string’s characters. For an external string, one whose characters live outside V8’s heap, that pointer does not keep the string alive. Preparing the second string could allocate memory, the allocation could start a garbage collection, and the collection could free the first string’s characters while V8 still pointed at them:
// src/objects/string.tq
macro TwoStringsToSlices<Result: type, Functor: type>(
s1: String, s2: String, f: Functor): Result {
+ // Flatten both strings before taking either slice. A slice into an external
+ // string is a raw pointer that doesn't keep the string alive, so flattening
+ // s2 while holding s1's slice could collect s1 and free its resource.
+ const s1 = Flatten(s1);
+ const s2 = Flatten(s2);
try {
StringToSlice(s1) otherwise FirstOneByte, FirstTwoByte;
The comment is V8’s own. The fix does all the work that can allocate memory before it takes any raw pointers.
Race conditions
V8 compiles code, collects garbage, and more and more often resizes buffers on background threads. A race condition happens when the main thread and a background thread disagree about the same object. One 2026 fix handles a typed array that a background thread grows while Object.values is reading it [14]:
// src/objects/elements.cc
size_t length = AccessorClass::GetCapacityImpl(*object, *elements);
+ // The TypedArray might have been grown by a background thread. Handle it
+ // gracefully.
+ if (length > max_nof_items) {
+ length = max_nof_items;
+ }
for (size_t index = 0; index < length; ++index) {
The result array was sized before this loop. If another thread grew the typed array in between, the loop could run past the end of the result. The fix caps the loop at the size the result was made for.
Another makes Maglev create a new number object for each deoptimization instead of sharing one [15].
Integer overflow
Integer overflows are usually size calculations: a length multiplied, a separator repeated, or a count that gets too big. Array.prototype.join now checks early for an overflow in the separator length [16]:
// src/builtins/array-join.tq
- // Detect integer overflow
- // TODO(turbofan): Replace with overflow-checked multiplication.
- if (sepsLen / separatorLength != nofSeparatorsInt) deferred {
- ThrowInvalidStringLength(context);
- }
...
+ if (separatorLength > 1 && len > 1) {
+ ...
+ const res = TryInt32Mul(lenInt - 1, sepLenInt) otherwise IfOverflow;
+ if (res > Convert<int32>(kStringMaxLength)) goto IfOverflow;
+ } label IfOverflow {
+ ThrowInvalidStringLength(context);
+ }
+ }
The old code multiplied first and checked for overflow afterwards. The fix does an overflow-checked multiply at the start, before any work is done.
Maglev also now stops compiling when an input node gets too big [17].
Logic bugs
“Inappropriate implementation” covers bugs where V8 does something the language does not allow, without a memory bug in the fix itself. One 2026 example is in the parser: V8 skipped a temporal dead zone check for hoisted functions inside sloppy-mode eval [18]. The fix moves six lines:
// src/ast/scopes.cc
if (scope->outer_scope_ == outer_scope_end) break;
+ if (scope->is_hoisted_in_context()) {
+ *access_position = scope->outer_scope()->start_position();
+ } else if (scope->is_eval_scope()) {
+ *access_position = scope->AsDeclarationScope()->eval_position();
+ }
+
if (V8_UNLIKELY(scope->is_dynamic_scope())) {
...
- if (scope->is_hoisted_in_context()) {
- *access_position = scope->outer_scope()->start_position();
- } else if (scope->is_eval_scope()) {
- *access_position = scope->AsDeclarationScope()->eval_position();
- }
V8 decides whether a variable needs a temporal dead zone check by comparing where it is used with where it is declared. The old code updated that position too late for code inside eval, so the check could be skipped.
Another logic bug fix changed how for-of loops return values in the interpreter and all three compilers [19]. To find bugs like these you need to know the JavaScript specification, not only the engine.
Where the bugs are
Figure 6 compares where fixes went in 2019–2025 with where they went in 2026.

Figure 6. Share of V8 CVEs whose fix touched each area. A fix can touch more than one area, so the shares add up to more than 100%.
Three things changed.
TurboFan is no longer on top. From 2019 to 2025, 27% of V8 fixes touched TurboFan, far more than any other area. In 2026 that dropped to 15%. The number of TurboFan CVEs still went up, from about 8 a year to 18, but other areas went up faster.
The newer compilers caught up. Maglev, V8’s mid-tier optimizing compiler, went from 7% of fixes to 18% and is now the most common area. Turboshaft, the newer compiler backend used by TurboFan and WebAssembly, went from 5% to 11%. Both are newer code that has been fuzzed for fewer years.
The debugger showed up. The debugger and inspector went from 4% to 16%. This code has always been part of V8, but security research rarely looked at it, partly because web pages usually cannot reach it.
In 2026, 19 CVEs had fixes in debugger or inspector code. 11 of them are use-after-frees, and only 5 are rated High, which fits code that usually needs a debugger attached.
A closer look at Maglev
Maglev is V8’s mid-tier optimizing compiler. It shipped in Chrome 117 in 2023 and sits between Sparkplug, which compiles quickly but simply, and TurboFan, which compiles slowly but makes the fastest code. Its job is to make good-enough code quickly.
In 2026, 22 V8 CVEs had fixes in Maglev. 19 of them are rated High and 12 are type confusions, and one of them, the sort bug above, was exploited in the wild. Unlike the debugger bugs, these are the kind attackers look for.
Several are the same mistake in one place. To make code faster, Maglev skips checks it can prove are not needed. One of these checks makes sure a value is a small integer, which V8 calls a Smi. At least five 2026 fixes tighten when Maglev may skip it, and two of them change the same function [20][21]:
// src/maglev/maglev-graph-builder.cc, in BuildCheckSmi()
- // For constants, we may be able to skip the runtime check.
- if (std::optional<int32_t> constant_value = TryGetInt32Constant(object)) {
- if (Smi::IsValid(constant_value.value())) return object;
+ // For non-tagged constants, we may be able to skip the runtime check ...
+ if (object->value_representation() != ValueRepresentation::kTagged) {
+ if (std::optional<int32_t> constant_value = TryGetInt32Constant(object)) {
+ if (Smi::IsValid(constant_value.value())) return object;
+ }
}
Maglev skipped the check when it knew a value was equal to a small number. But a JavaScript value can equal a small number and still be stored as a full number object instead of a Smi. The fix only skips the check for raw machine numbers, where the value is all that matters.
What still gets exploited
Bugs used in real attacks look different. Since 2016, Chrome has published 75 CVEs exploited in the wild, and 41 of them were in V8. Figure 7 compares the bug classes of all V8 CVEs in that period with the exploited ones.

Figure 7. Bug class share of all V8 CVEs since 2016 (grey) and of those exploited in the wild (orange).
Type confusions are 31% of all V8 CVEs but 46% of the exploited ones. Use-after-frees go the other way: 14% of all V8 CVEs, but only 5% of the exploited ones.
Figure 8 shows the same gap by area.

Figure 8. Share of V8 CVEs since 2016 whose fix touched each area, for all V8 CVEs (grey) and for those exploited in the wild (orange).
TurboFan appears in 23% of all V8 fixes but in 50% of the exploited ones. WebAssembly goes the other way, 16% against 6%, and none of the exploited bugs were fixed in the debugger.
In 2026, 4 of the 131 V8 CVEs were exploited in the wild: one type confusion (the sort bug above [9]), two out-of-bounds bugs, and one logic bug. That is too few to show a trend on its own.
A note on the V8 sandbox
Not every V8 security fix gets a CVE. The V8 sandbox is meant to keep memory corruption contained inside the engine, and it gets its own hardening fixes, which usually have no CVE. v8bugs.fyi tracks these separately and counts 128 so far in 2026. The fix titles we looked at cover the same kinds of problems as above: races on shared tables, values read twice from memory an attacker can change, and state passed between trusted and untrusted objects.
What this means for researchers
More V8 bugs are being found, in more parts of the engine, and Google says AI now helps both find and fix them. That is good for users.
But the bugs attackers use have barely changed. Type confusions are still the most common kind, often in the optimizing compilers. And since 2023, the new findings have looked less like them each year: type confusions fell from 84% of V8 CVEs in 2023 to 24% in 2026, while they make up 46% of the V8 bugs exploited in the wild.
That gap is where we think research time is best spent. Finding these bugs takes deep knowledge of one part of the engine and a way to aim a search at it, which is what we built SeekZero for.
References
- Tashita, “AI Browser Vulnerability Research Part 1: The Rise of AI-Driven Browser Vulnerability Research.” https://tashita.net/ai-browser-vulnerability-research-part-1/
- v8-cves. https://github.com/str8outtaheap/v8-cves
- chrome-cves. https://github.com/str8outtaheap/chrome-cves
- Chrome Releases. https://chromereleases.googleblog.com/
- V8 blog, “Maglev – V8’s Fastest Optimizing JIT,” December 5, 2023. https://v8.dev/blog/maglev
- v8bugs.fyi. https://v8bugs.fyi/
- Chrome Security Team, “Stronger with every update: How we’re making Chrome and the web safer in the AI Era,” July 30, 2026. https://blog.google/security/chrome-stronger-with-every-update/
- Google DeepMind, “Introducing Gemini 3.5 Flash Cyber,” July 21, 2026. https://deepmind.google/blog/introducing-gemini-3-5-flash-cyber/
- CVE-2026-85046 fix: “[compiler] Don’t inline Array.prototype.sort on mixed elements kinds.” https://chromium.googlesource.com/v8/v8/+/e0562d87ad9c17042b581582c99237d798572e67
- CVE-2026-87564 fix: “[builtins] Fix Array.prototype.flat.” https://chromium.googlesource.com/v8/v8/+/50a6c0c69c1384720d9c3599dadfa73e55b865d7
- CVE-2026-15903 fix: “[turbofan] Keep safe-integer check in ToNumber Word32 lowering.” https://chromium.googlesource.com/v8/v8/+/a7835b8c294c97d6d6cbfc22582de40cfcbf0c19
- CVE-2026-95304 fix: “[compiler] ISel: Fix VisitInt32Sub for 32-bit lhs.” https://chromium.googlesource.com/v8/v8/+/057dcc408de1583cf68c4034bbba89d0f78a88a4
- CVE-2026-19556 fix: “[string] Fix UAF in TwoStringsToSlices with external strings.” https://chromium.googlesource.com/v8/v8/+/9062318d79629d4084053747fec654a1a49dc690
- CVE-2026-5893 fix: “[RAB/GSAB] Object.values: Handle a TA grown by a background thread gracefully.” https://chromium.googlesource.com/v8/v8/+/02cd73dfc58dbe73054382c85f9f126af9ce597e
- CVE-2026-85045 fix: “[maglev] Materialize a fresh HeapNumber per deoptimization.” https://chromium.googlesource.com/v8/v8/+/7d5eb1df2a721c8fe3aa03b999464c4b86242080
- CVE-2026-14430 fix: “Check early for separator overflow in Array.prototype.join().” https://chromium.googlesource.com/v8/v8/+/8c3c9f464a45a8809581ff45382bb46d01eb9171
- CVE-2025-10892 fix: “[maglev] Abort compilation if input node is too big.” https://chromium.googlesource.com/v8/v8/+/618d1a029005753ca036166b9b0684c7bc333077
- CVE-2026-10935 fix: “[ast] Fix hole check elision for hoisted functions in sloppy eval.” https://chromium.googlesource.com/v8/v8/+/ad07ea9d88fd5c2a529d35fb4ebec046f5404b99
- CVE-2026-14383 fix: “[for-of] Use a single return value.” https://chromium.googlesource.com/v8/v8/+/02a725e2a0762f39266fdac2ba2869c71af37ed2
- CVE-2026-7337 fix: “[maglev] Restrict BuildCheckSmi constant elision to non-tagged inputs.” https://chromium.googlesource.com/v8/v8/+/cc9a97f192319ed218c191fa20cbecd1bf51cb71
- CVE-2026-5865 fix: “[maglev] Avoid eliding Smi checks too aggressively.” https://chromium.googlesource.com/v8/v8/+/fac60cc3fdf84591594354fb3629a2fcb376ff23
Keep reading
Related research
- Where Pixels Go Wrong: Chrome’s Graphics Stack: 506 Chrome graphics CVEs in 2026.
- CVE-2026-6307 (Part 1): Turbofan JS-to-Wasm Deopt Type Confusion: root cause and trigger.
- AI Browser Vulnerability Research, Part 1: the rise of AI-driven browser vulnerability research.
Learn more about SeekZero, our browser vulnerability research framework, or browse all posts on the Tashita research blog.
