1. Understanding eval(): Executing JavaScript Through Strings
JavaScript allows code to be generated and executed dynamically at runtime. One of the most commonly used mechanisms for this is eval(), a built-in JavaScript function that takes a string containing JavaScript code and executes it. We can see this in the following example:
Although "value + 1" is represented as a string, eval() interprets its contents as JavaScript code. The evaluated expression can access the variable value declared in the same scope.
This behavior introduces additional complexity for JavaScript engines. Unlike conventional code, which can be analyzed before execution begins, the source code provided to eval() may not be available until the moment it is executed. The engine must then parse, compile, and execute this new code while the original program is already running.
Direct and Indirect Eval
In JavaScript, there are two ways to use eval(): direct eval and indirect eval.
A direct eval occurs when eval(...) is called directly, using the eval function provided by JavaScript. In this case, the evaluated code can access the variables available in the environment where the call is made.
An indirect eval occurs when eval is called through another variable, such as const execute = eval; execute(...), or through expressions like globalThis.eval(...) and (0, eval)(...). In these cases, the code executes in the global scope and cannot access local variables from the environment where the call is made.
The second and third calls produce a ReferenceError: local is not defined because indirect eval cannot access the function’s local scope.
Strict mode also introduces additional differences. In non-strict code (sloppy mode), a direct eval can introduce var declarations into the function that contains it. In strict mode, declarations made inside the evaluated text remain in their own variable environment.
These differences directly affect how engines resolve variables, manage execution contexts, and compile dynamically generated code.
Defining eval in One Scope and Executing It in Another
The distinction between direct and indirect eval becomes more complex when we pass eval as an argument to another function.
For a call to be direct, it must use the identifier eval, and that identifier must reference the original JavaScript eval function corresponding to the environment where the call is made.
Both calls execute the same function, but their behavior changes depending on the value passed as an argument.
In the first case, the parameter contains the original eval function, so the call is direct and can access local. In the second, bind() creates a new function that no longer meets the conditions for direct eval. The code executes in the global scope, where local is not defined.
The syntax inside run() never changes, but the execution behavior does.
To determine how a call to eval(...) should be executed, the parser identifies potentially direct calls, and the engine checks at runtime whether the invoked function corresponds to the built-in eval function.
Closures and the Lifetime of Evaluated Scopes
Code executed by eval can create functions that continue accessing variables from their environment, even after the containing function has finished executing.
In this example, eval() creates an arrow function that maintains access to count through a closure.
In this other example, although secret is not directly accessible from keep(), the function assigned to globalThis.peek retains a reference to the environment created during evaluation.
For JavaScript engines, this means that certain states need to remain in memory long after the original eval call has finished, generating complex behaviors and interactions between variables, execution contexts, closures, and the garbage collector.
What Happens Inside the JavaScript Engine?
When an engine encounters an eval() call that executes code, it must process that code through several phases:
- Parsing: Tokenize and parse the string according to JavaScript grammar.
- Scope analysis: Resolve identifiers and declarations while taking the corresponding execution environment into account.
- Bytecode generation: Compile the source code into an executable representation.
- Execution: Execute the generated code, potentially involving JIT compilation and additional optimizations.
The implementation varies between V8, SpiderMonkey, and JavaScriptCore, but they all share certain aspects that make dynamic evaluation relevant to security research:
- Interaction with existing scopes. A direct eval can access variables declared before the evaluated code existed, including variables captured by closures or declared in outer blocks.
- Impact on optimizations. Dynamically generated code can access or modify variables by name, complicating the assumptions used by optimizing compilers. Engines may need to retain additional scope information or restrict certain optimizations.
- Compilation and caching. Parsing and compiling code at runtime introduces additional overhead. Engines such as V8 maintain compilation caches for eval, considering information such as the source code, the calling function, strict mode, and the scope position. Reusing compiled code requires ensuring that it remains valid for the current execution environment.
Security Implications
From a JavaScript engine security perspective, dynamic code execution can generate complex states and interactions within the engine. Errors in how these are handled can cause inconsistencies and lead to vulnerabilities.
For security researchers, these behaviors provide new opportunities to explore execution paths, identify complex states, and discover potential vulnerabilities.
As we will see in the next section, some of these interactions have already led to real vulnerabilities.
2. eval() and JavaScript Engine Vulnerabilities
As we have seen, eval() affects how JavaScript engines manage scopes and resolve variables. This requires specific handling during code analysis and compilation.
One example is CVE-2026-10935, a vulnerability fixed in Chrome 149, where the presence of eval("") caused an error in variable resolution.
CVE-2026-10935
To understand this vulnerability, we first need to understand how JavaScript handles variables declared with let and const. These variables cannot be accessed before their declaration is executed. This behavior is known as the Temporal Dead Zone (TDZ).
For example:
Internally, V8 uses a special value, known as a sentinel value, called the hole, to represent variables that have not yet been initialized.
When the program attempts to access one of these variables, the engine checks whether it contains that value and, if so, throws a ReferenceError.
To improve performance, V8 can eliminate some of these checks when it determines that a variable will already be initialized by the time it is accessed.
The problem arises when that conclusion is incorrect.
The Vulnerability
The following code is part of the regression test published alongside the fix:
In this example, __f_9() executes during the initialization of v1. Inside the function, the code attempts to access that same variable through v5.set(v1).
Since v1 has not yet been initialized, the correct behavior is to throw a ReferenceError.
However, in vulnerable versions of V8, the check could be incorrectly eliminated, allowing the internal value the hole to reach Map.prototype.set().
What role does eval() play in this case?
As we saw in the first section, a direct eval can interact with variables in its containing scope. For this reason, when V8 encounters an eval() call inside a function in sloppy mode, it must account for the possibility that its variables may be resolved dynamically.
In this example, the presence of eval("") causes V8 to treat the scope of __f_9() as dynamic.
The problem was in the variable resolution process. V8 uses different paths to resolve identifiers depending on the scope type, and the path used for dynamic scopes skipped a check related to function declarations.
These functions can be called before their position in the source code, as happens with __f_9(). Therefore, the engine must consider that their references to outer variables may execute before those variables have been initialized.
In the vulnerable case, V8 incorrectly used the position of the reference to v1 inside __f_9(). Since that position appeared after the declaration of v1 in the source code, the engine considered the variable already initialized and eliminated the TDZ check.
This allowed the hole to leak and reach Map.prototype.set() as if it were a valid JavaScript value.
Why Is the hole Important?
The hole is an internal V8 value that should not be directly accessible from JavaScript.
The interpreters, optimizing compilers, and internal engine functions operate under this assumption. If the value reaches operations that expect conventional JavaScript values, it can cause inconsistencies and potentially lead to exploitation.
This type of issue had already appeared in V8. For example, CVE-2021-38003, a vulnerability exploited in 2021, also allowed the hole to be exposed and used to cause memory corruption.
The Importance of eval() in This Vulnerability
Regarding CVE-2026-10935, we can observe that the problem does not depend on the code executed by eval, but on its presence inside the function. The string is empty and contains no operations, but even so, the engine must account for the scope resolution rules associated with dynamic evaluation.
This case demonstrates the importance of combining eval() with other JavaScript constructs when conducting vulnerability research.
3. CodeString and EvalCallExpression in SeekZero
As we have seen, dynamic JavaScript code execution introduces additional complexity into JavaScript engines, making it an area of interest for vulnerability research through fuzzing.
To explore these behaviors, we have extended SeekZero with new capabilities to generate and mutate JavaScript code inside strings, taking its grammatical structure into account.
To achieve this, we have introduced three new components: CodeString, EvalCallExpression, and the CodeString datatype.
CodeString: Generating JavaScript Inside Strings

CodeString allows SeekZero to generate strings containing JavaScript source code.
Unlike conventional text generation, CodeString takes into account the grammatical structure of the JavaScript it produces and its environment, allowing it to generate valid code statements and expressions.
Consider the following example:
Although code is a conventional string, SeekZero can work directly with the JavaScript it contains. For example, it can modify components of the twice() function, add or mutate statements, or remove parts of the code.
During generation and mutation, SeekZero takes JavaScript grammar rules into account, including strict mode restrictions.
Generating Nested JavaScript
CodeString also allows eval() calls to be generated and mutated recursively, exploring different levels of dynamic execution.
For example:
Beyond eval()
CodeString is not limited to eval().
JavaScript also allows code to be compiled dynamically using the Function constructor and its variants for generator, async, and async generator functions.
SeekZero can generate and mutate the bodies of functions compiled dynamically through these mechanisms, following the corresponding rules.
EvalCallExpression: Exploring Different Forms of eval()

As explained in the first section, JavaScript distinguishes between direct and indirect eval calls.
To explore these behaviors, we have introduced EvalCallExpression, a custom node that supports different ways of invoking eval().
SeekZero can generate the following variants:
| Form | Evaluation |
|---|---|
eval(...) |
Direct |
(eval)(...) |
Direct |
(0, eval)(...) |
Indirect |
globalThis.eval(...) |
Indirect |
eval?.(...) |
Indirect |
Each variant can be selected during generation or introduced through mutations, allowing researchers to explore different execution contexts.
SeekZero also supports references to the eval function previously stored in variables, properties, arrays, or other structures.
For example:
Preserving Dependencies Between Scopes
Consider:
In this non-strict function, direct eval can access local_1 and introduce variable_3 into the function’s scope.
Changing the call to indirect eval would remove access to local_1 and modify the scope in which declarations are introduced. To avoid unnecessary inconsistencies, SeekZero takes these dependencies between scopes into account when applying mutations, preserving direct evaluation when the program requires it.
CodeString Datatype: Reusing Dynamically Generated Code

As explained earlier, code can be created in one scope and executed later in another. The same string can also be evaluated multiple times, potentially involving different execution contexts and compilation cache behaviors.
To support these patterns, SeekZero introduces the CodeString datatype, which represents strings containing JavaScript source code.
When a program needs a code string, SeekZero can generate a new one or reuse an existing one that was previously stored.
For example:
The same code string can also be evaluated repeatedly, using different forms of eval or from different scopes.
These scenarios are relevant to compilation caching mechanisms, where engines must ensure that previously compiled code remains valid when reused.
Integrating CodeString into Mutation Plans

The new capabilities are also integrated into SeekZero’s mutation system.
When creating a Mutation Plan from a JavaScript program, SeekZero recognizes calls to eval() and the JavaScript code contained within their strings.
This allows us to select statements or expressions inside a CodeString and configure specific mutations for them, just as we would with any other part of the program.
4. Demo: Generating Dynamic JavaScript with SeekZero
References
1. Understanding eval(): Executing JavaScript Through Strings
JavaScript allows code to be generated and executed dynamically at runtime. One of the most commonly used mechanisms for this is eval(), a built-in JavaScript function that takes a string containing JavaScript code and executes it. We can see this in the following example:
Although "value + 1" is represented as a string, eval() interprets its contents as JavaScript code. The evaluated expression can access the variable value declared in the same scope.
This behavior introduces additional complexity for JavaScript engines. Unlike conventional code, which can be analyzed before execution begins, the source code provided to eval() may not be available until the moment it is executed. The engine must then parse, compile, and execute this new code while the original program is already running.
Direct and Indirect Eval
In JavaScript, there are two ways to use eval(): direct eval and indirect eval.
A direct eval occurs when eval(...) is called directly, using the eval function provided by JavaScript. In this case, the evaluated code can access the variables available in the environment where the call is made.
An indirect eval occurs when eval is called through another variable, such as const execute = eval; execute(...), or through expressions like globalThis.eval(...) and (0, eval)(...). In these cases, the code executes in the global scope and cannot access local variables from the environment where the call is made.
The second and third calls produce a ReferenceError: local is not defined because indirect eval cannot access the function’s local scope.
Strict mode also introduces additional differences. In non-strict code (sloppy mode), a direct eval can introduce var declarations into the function that contains it. In strict mode, declarations made inside the evaluated text remain in their own variable environment.
These differences directly affect how engines resolve variables, manage execution contexts, and compile dynamically generated code.
Defining eval in One Scope and Executing It in Another
The distinction between direct and indirect eval becomes more complex when we pass eval as an argument to another function.
For a call to be direct, it must use the identifier eval, and that identifier must reference the original JavaScript eval function corresponding to the environment where the call is made.
Both calls execute the same function, but their behavior changes depending on the value passed as an argument.
In the first case, the parameter contains the original eval function, so the call is direct and can access local. In the second, bind() creates a new function that no longer meets the conditions for direct eval. The code executes in the global scope, where local is not defined.
The syntax inside run() never changes, but the execution behavior does.
To determine how a call to eval(...) should be executed, the parser identifies potentially direct calls, and the engine checks at runtime whether the invoked function corresponds to the built-in eval function.
Closures and the Lifetime of Evaluated Scopes
Code executed by eval can create functions that continue accessing variables from their environment, even after the containing function has finished executing.
In this example, eval() creates an arrow function that maintains access to count through a closure.
In this other example, although secret is not directly accessible from keep(), the function assigned to globalThis.peek retains a reference to the environment created during evaluation.
For JavaScript engines, this means that certain states need to remain in memory long after the original eval call has finished, generating complex behaviors and interactions between variables, execution contexts, closures, and the garbage collector.
What Happens Inside the JavaScript Engine?
When an engine encounters an eval() call that executes code, it must process that code through several phases:
- Parsing: Tokenize and parse the string according to JavaScript grammar.
- Scope analysis: Resolve identifiers and declarations while taking the corresponding execution environment into account.
- Bytecode generation: Compile the source code into an executable representation.
- Execution: Execute the generated code, potentially involving JIT compilation and additional optimizations.
The implementation varies between V8, SpiderMonkey, and JavaScriptCore, but they all share certain aspects that make dynamic evaluation relevant to security research:
- Interaction with existing scopes. A direct eval can access variables declared before the evaluated code existed, including variables captured by closures or declared in outer blocks.
- Impact on optimizations. Dynamically generated code can access or modify variables by name, complicating the assumptions used by optimizing compilers. Engines may need to retain additional scope information or restrict certain optimizations.
- Compilation and caching. Parsing and compiling code at runtime introduces additional overhead. Engines such as V8 maintain compilation caches for eval, considering information such as the source code, the calling function, strict mode, and the scope position. Reusing compiled code requires ensuring that it remains valid for the current execution environment.
Security Implications
From a JavaScript engine security perspective, dynamic code execution can generate complex states and interactions within the engine. Errors in how these are handled can cause inconsistencies and lead to vulnerabilities.
For security researchers, these behaviors provide new opportunities to explore execution paths, identify complex states, and discover potential vulnerabilities.
As we will see in the next section, some of these interactions have already led to real vulnerabilities.
2. eval() and JavaScript Engine Vulnerabilities
As we have seen, eval() affects how JavaScript engines manage scopes and resolve variables. This requires specific handling during code analysis and compilation.
One example is CVE-2026-10935, a vulnerability fixed in Chrome 149, where the presence of eval("") caused an error in variable resolution.
CVE-2026-10935
To understand this vulnerability, we first need to understand how JavaScript handles variables declared with let and const. These variables cannot be accessed before their declaration is executed. This behavior is known as the Temporal Dead Zone (TDZ).
For example:
Internally, V8 uses a special value, known as a sentinel value, called the hole, to represent variables that have not yet been initialized.
When the program attempts to access one of these variables, the engine checks whether it contains that value and, if so, throws a ReferenceError.
To improve performance, V8 can eliminate some of these checks when it determines that a variable will already be initialized by the time it is accessed.
The problem arises when that conclusion is incorrect.
The Vulnerability
The following code is part of the regression test published alongside the fix:
In this example, __f_9() executes during the initialization of v1. Inside the function, the code attempts to access that same variable through v5.set(v1).
Since v1 has not yet been initialized, the correct behavior is to throw a ReferenceError.
However, in vulnerable versions of V8, the check could be incorrectly eliminated, allowing the internal value the hole to reach Map.prototype.set().
What role does eval() play in this case?
As we saw in the first section, a direct eval can interact with variables in its containing scope. For this reason, when V8 encounters an eval() call inside a function in sloppy mode, it must account for the possibility that its variables may be resolved dynamically.
In this example, the presence of eval("") causes V8 to treat the scope of __f_9() as dynamic.
The problem was in the variable resolution process. V8 uses different paths to resolve identifiers depending on the scope type, and the path used for dynamic scopes skipped a check related to function declarations.
These functions can be called before their position in the source code, as happens with __f_9(). Therefore, the engine must consider that their references to outer variables may execute before those variables have been initialized.
In the vulnerable case, V8 incorrectly used the position of the reference to v1 inside __f_9(). Since that position appeared after the declaration of v1 in the source code, the engine considered the variable already initialized and eliminated the TDZ check.
This allowed the hole to leak and reach Map.prototype.set() as if it were a valid JavaScript value.
Why Is the hole Important?
The hole is an internal V8 value that should not be directly accessible from JavaScript.
The interpreters, optimizing compilers, and internal engine functions operate under this assumption. If the value reaches operations that expect conventional JavaScript values, it can cause inconsistencies and potentially lead to exploitation.
This type of issue had already appeared in V8. For example, CVE-2021-38003, a vulnerability exploited in 2021, also allowed the hole to be exposed and used to cause memory corruption.
The Importance of eval() in This Vulnerability
Regarding CVE-2026-10935, we can observe that the problem does not depend on the code executed by eval, but on its presence inside the function. The string is empty and contains no operations, but even so, the engine must account for the scope resolution rules associated with dynamic evaluation.
This case demonstrates the importance of combining eval() with other JavaScript constructs when conducting vulnerability research.
3. CodeString and EvalCallExpression in SeekZero
As we have seen, dynamic JavaScript code execution introduces additional complexity into JavaScript engines, making it an area of interest for vulnerability research through fuzzing.
To explore these behaviors, we have extended SeekZero with new capabilities to generate and mutate JavaScript code inside strings, taking its grammatical structure into account.
To achieve this, we have introduced three new components: CodeString, EvalCallExpression, and the CodeString datatype.
CodeString: Generating JavaScript Inside Strings

CodeString allows SeekZero to generate strings containing JavaScript source code.
Unlike conventional text generation, CodeString takes into account the grammatical structure of the JavaScript it produces and its environment, allowing it to generate valid code statements and expressions.
Consider the following example:
Although code is a conventional string, SeekZero can work directly with the JavaScript it contains. For example, it can modify components of the twice() function, add or mutate statements, or remove parts of the code.
During generation and mutation, SeekZero takes JavaScript grammar rules into account, including strict mode restrictions.
Generating Nested JavaScript
CodeString also allows eval() calls to be generated and mutated recursively, exploring different levels of dynamic execution.
For example:
Beyond eval()
CodeString is not limited to eval().
JavaScript also allows code to be compiled dynamically using the Function constructor and its variants for generator, async, and async generator functions.
SeekZero can generate and mutate the bodies of functions compiled dynamically through these mechanisms, following the corresponding rules.
EvalCallExpression: Exploring Different Forms of eval()

As explained in the first section, JavaScript distinguishes between direct and indirect eval calls.
To explore these behaviors, we have introduced EvalCallExpression, a custom node that supports different ways of invoking eval().
SeekZero can generate the following variants:
| Form | Evaluation |
|---|---|
eval(...) |
Direct |
(eval)(...) |
Direct |
(0, eval)(...) |
Indirect |
globalThis.eval(...) |
Indirect |
eval?.(...) |
Indirect |
Each variant can be selected during generation or introduced through mutations, allowing researchers to explore different execution contexts.
SeekZero also supports references to the eval function previously stored in variables, properties, arrays, or other structures.
For example:
Preserving Dependencies Between Scopes
Consider:
In this non-strict function, direct eval can access local_1 and introduce variable_3 into the function’s scope.
Changing the call to indirect eval would remove access to local_1 and modify the scope in which declarations are introduced. To avoid unnecessary inconsistencies, SeekZero takes these dependencies between scopes into account when applying mutations, preserving direct evaluation when the program requires it.
CodeString Datatype: Reusing Dynamically Generated Code

As explained earlier, code can be created in one scope and executed later in another. The same string can also be evaluated multiple times, potentially involving different execution contexts and compilation cache behaviors.
To support these patterns, SeekZero introduces the CodeString datatype, which represents strings containing JavaScript source code.
When a program needs a code string, SeekZero can generate a new one or reuse an existing one that was previously stored.
For example:
The same code string can also be evaluated repeatedly, using different forms of eval or from different scopes.
These scenarios are relevant to compilation caching mechanisms, where engines must ensure that previously compiled code remains valid when reused.
Integrating CodeString into Mutation Plans

The new capabilities are also integrated into SeekZero’s mutation system.
When creating a Mutation Plan from a JavaScript program, SeekZero recognizes calls to eval() and the JavaScript code contained within their strings.
This allows us to select statements or expressions inside a CodeString and configure specific mutations for them, just as we would with any other part of the program.
4. Demo: Generating Dynamic JavaScript with SeekZero
References
