Skip to content

Generated code drops required parentheses around in inside a for-init arrow body → unparsable output ("Invalid left-hand side in for-loop") #1419

Description

@VolatilCapital

Expected Behavior

Obfuscating valid input should always produce syntactically valid output (or throw). Parentheses around an in expression inside a for statement's init are grammatically required (the "NoIn" restriction) and must be preserved.

Current Behavior

The obfuscator silently emits unparsable JavaScript. It removes the parentheses around an in expression located in a for init, so the bare in is parsed as a for-in head and the code throws at parse time:

  • V8 / native: SyntaxError: Invalid left-hand side in for-loop
  • acorn: Assigning to rvalue

No exception is raised by obfuscate() — the invalid code is returned as-is, which can ship and break the application at load.

Steps to Reproduce

Input (valid JS):

for(a=(t,e)=>(t in e)?0:1;;){}
import O from 'javascript-obfuscator'
const out = O.obfuscate(`for(a=(t,e)=>(t in e)?0:1;;){}`, { compact: true }).getObfuscatedCode()
console.log(out)
// for(a=(_0x14502c,_0xcad55a)=>_0x14502c in _0xcad55a?0x0:0x1;;){}
//                              ^^^^^^^^^^^^^^^^^^^^^^^ parentheses around `(t in e)` dropped
new Function(out) // throws: Invalid left-hand side in for-loop

The protective parentheses present in the input ((t in e)) are absent from the output.

Reproduces regardless of options

Same broken output with default options, with { compact: true } only, and even with { simplify: false } — so this is in the code generator / parenthesization logic, not a specific transform:

Options Result
{} (defaults) ❌ Invalid left-hand side in for-loop
{ compact: true }
{ compact: true, simplify: false }

Root cause

Inside a for statement's init, an unparenthesized in operator is interpreted as introducing a for-in loop (the ECMAScript "NoIn" grammar production). The input parenthesizes the in expression to opt out of that interpretation; the obfuscator's output omits those parentheses, so the parser consumes everything before in as the for-in binding target — which is not a valid assignment target — hence the parse error.

For comparison, the same in expression outside a for init (e.g. var a = (b in c) ? 1 : 2;) is correctly emitted without parentheses and stays valid — confirming the parentheses are only required in the for-init "NoIn" context, which the generator does not account for.

Real-world impact

Hit in a production bundle: obfuscating an app bundle that includes @sentry/rrweb code (a base64 char-code table built via for (h = (e,t,n) => (t in e) ? define(e,t,…) : e[t]=n, i=0; i<s.length; i++) …) produced an unparsable bundle. On a single-file (inlined) native bundle this bricks the app at load — the JS never parses. Minimal reduction of that bundle yields the one-line repro above.

Your Environment

  • javascript-obfuscator: 5.4.3 (latest)
  • Node.js: 22.x
  • OS: Linux

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions