The eval
Trick
Each direct eval
creates a separate browser execution context, so the idea is to use it to break down scripts without the need of including additional scripts. The high-level idea is to split each script and subsequently each block recursively into blocks of eval
calls smaller than 1kB (an arbitrary number), and somehow have the whole script behave as before.
- Test run in the browser.
- Crawl top 100 websites and analyze the logs.
Why it should work
Direct eval
calls are executed in the same scope as the caller, with read-write access to all of the surrounding variables.
Challenges and workarounds
In reality, the eval
trick is a giant hack due to the quirks of eval
.
Hoisting: some constructs behave as if they are evaluated first before the script is run.
Top-level functions.
We simply
eval
them first.Variable declarations. Functions cannot capture variables declared in adjacent
eval
s, and would capture the variable in the outer scope with the same name instead, causing errors. Variables created ineval
do not leak out unless they are declared withvar
in non-strict mode.We identify all variable declarations in the current scope, declare all of them first using
var
orlet
, then make sure they are not declared again in theeval
calls on the same level.(Deferred) Some assignments do not have a single variable identifier on their left-hand side (LHS), such as destructuring. The LHS can contain arbitrary expressions.
We currently stick
var
to their beginning so most of them work, but the assigned variables do not leak out. This is usually fine because most such assignments are for local variables anyway.
return
statements cannot return from insideeval
.For each
eval
block that containsreturn
statements not in nested functions or classes, we wrap it in an immediately invoked function expression (IIFE) so that thereturn
s are valid. We call the IIFE with.call(this)
to preservethis
. We then check the return value ofeval
and return early it if it is notundefined
.Functions declared in IIFEs do not leak out.
We declare the function identifiers as variables first, then convert all function declarations to assignments of function expressions to those variables.
(Deferred) This seems to break a few scripts, especially when they call functions defined in other scripts.
No idea for solutions yet because the error messages did not reveal the cause.
import
statements are not allowed insideeval
.We put them at the top of the script.
export
statements are not allowed insideeval
.We keep these scripts as is.
break
,continue
andyield
statements may not be able to reach the correct outer scopes insideeval
.We do not rewrite with
eval
in loops or generators until we hit a function or class boundary.await
does not work insideeval
.We
await
on theeval
and use an async IIFE if an IIFE is used.
Inherent limitations
- Performance:
eval
disables the JIT and forces slow variable lookups. - Bloat: deeply nested
eval
s cause mountains of backslashes. - Integrity: e.g., if the website checks the checksum of the script, it will fail.