Back to Blog
critical SEVERITY6 min read

How unsafe eval() code execution happens in JavaScript game scripting and how to fix it

A critical arbitrary code execution vulnerability was discovered in `scripts/CommandBlock.js` where user-provided input from a text dialog was directly concatenated into an `eval()` call without any sanitization or sandboxing. The fix replaces the dangerous `eval()` with a `new Function()` constructor, which provides better scope isolation and eliminates the string concatenation injection vector.

O
By Orbis AppSec
Published July 28, 2026Reviewed July 28, 2026

Answer Summary

This is an unsafe eval() vulnerability (CWE-95: Eval Injection) in JavaScript game scripting code where user input from a command block dialog is directly concatenated into an eval() statement, enabling arbitrary code execution. The fix replaces `eval("try{ " + text + "} catch(e) {...}")` with `try { (new Function(text))(); } catch(e) {...}`, which eliminates the string concatenation vector and provides scope isolation by not exposing local variables to the executed code.

Vulnerability at a Glance

cweCWE-95
fixReplace eval() with new Function() constructor wrapped in proper try/catch
riskComplete arbitrary code execution within the game's scripting environment
languageJavaScript
root causeUser input directly concatenated into eval() without sanitization
vulnerabilityEval Injection / Arbitrary Code Execution

How unsafe eval() code execution happens in JavaScript game scripting and how to fix it

Introduction

In the CommandBlock.js script of a Mindustry game mod, we discovered a critical arbitrary code execution vulnerability at line 209. The script handles in-game command blocks — interactive elements that let players trigger actions — and includes a "run-javascript" option that passes user input directly into eval(). The vulnerable line concatenated player-provided text into an eval string: eval("try{ " + text + "} catch(e) { Vars.ui.showText(error,e)}"). This pattern gives any player who interacts with the command block full access to the game's JavaScript runtime, including game state manipulation, rule changes, and potential denial-of-service attacks.

For developers building modding systems, game scripting engines, or any application that accepts user-provided code, this vulnerability illustrates exactly why eval() with string concatenation is considered one of the most dangerous patterns in JavaScript.

The Vulnerability Explained

The CommandBlock.js file handles tap events on command block entities. When a player selects the "run-javascript" option from the command block menu, a text input dialog appears. Whatever the player types is stored in the text variable and then executed:

const error = Core.bundle.format("commandblock.showtoast.run-javascript-2");
lastCommand = text;
eval("try{ " + text + "} catch(e) { Vars.ui.showText(error,e)}");

There are two critical problems here:

1. Direct string concatenation into eval(): The user's input (text) is concatenated directly into the eval string. This means a player can break out of the intended try block structure. For example, inputting } finally { /* malicious code */ } // would alter the control flow entirely.

2. Full local scope access: When eval() runs, it has access to the entire local scope — including the error variable, lastCommand, and any other variables in the enclosing Events.on(EventType.TapEvent, e => {...}) handler. This means executed code can modify the event handler's behavior for subsequent invocations.

Concrete Attack Scenarios

A player accessing the command block could enter:

  • Game rule manipulation: Vars.state.rules.editor = true; — enables editor mode, bypassing intended game restrictions
  • Denial of service: while(true){} — freezes the game with an infinite loop
  • State corruption: Vars.state.wave = 99999; — skips to an impossible wave, corrupting game progression
  • Scope escape: } catch(x){} Vars.state.rules.attackMode = true; try{ — breaks out of the try block structure to execute code outside the intended error handling

Because the game's scripting environment exposes Vars, Core, Sounds, and other powerful APIs, the impact extends to anything the game engine can do.

The Fix

The fix at line 211 replaces the dangerous eval() concatenation with a new Function() constructor wrapped in a proper try/catch block:

Before (vulnerable):

eval("try{ " + text + "} catch(e) { Vars.ui.showText(error,e)}");

After (fixed):

try { (new Function(text))(); } catch(e) { Vars.ui.showText(error, e); }

This change provides three specific security improvements:

1. Eliminates string concatenation injection: The user's input is no longer concatenated into a code string. It's passed as the body of a new function, meaning the player cannot break out of the try/catch structure by injecting } characters.

2. Scope isolation: new Function() creates a function in the global scope, not the local scope. The executed code cannot access error, lastCommand, e (the tap event), or any other local variables from the event handler. This significantly reduces the attack surface.

3. Proper error handling: The try/catch is now a real JavaScript control structure, not a string that gets parsed. This means error handling works correctly regardless of what the user inputs — even syntax errors are caught properly, whereas the old eval approach could fail to catch errors if the user's input broke the try/catch string structure.

Note that while new Function() still allows code execution (which is the intended feature of the "run-javascript" command block), it does so with proper isolation and without the injection amplification that string concatenation into eval creates.

Prevention & Best Practices

Avoid eval() entirely

The ESLint no-eval rule should be enabled in any JavaScript project. In virtually every case where eval() is used, a safer alternative exists:

Instead of Use
eval("obj." + prop) obj[prop]
eval("try{" + code + "}...") try { new Function(code)() }
eval(jsonString) JSON.parse(jsonString)

For game scripting systems specifically:

  1. Allowlist available APIs: If possible, create a sandboxed context that only exposes specific game APIs rather than the entire runtime
  2. Use Web Workers or iframes: For browser-based games, execute user code in an isolated context
  3. Implement timeouts: Wrap user code execution with setTimeout checks or use a termination mechanism to prevent infinite loops
  4. Input validation: Even with new Function(), consider validating input against known dangerous patterns

Detection tools:

  • ESLint: Enable no-eval and no-new-func rules
  • Semgrep: Use rules targeting eval() with tainted input
  • CodeQL: JavaScript security queries detect eval injection patterns

Key Takeaways

  • Never concatenate user input into eval() strings — the "try{ " + text + "} catch(e){...}" pattern in CommandBlock.js allowed players to break out of the intended control flow structure
  • new Function() provides scope isolation that eval() does not — the fix prevents access to local variables like error, lastCommand, and the tap event object e
  • String-based try/catch is fragile — wrapping eval'd code in a string try/catch can be bypassed by injecting closing braces; real try/catch blocks cannot be escaped this way
  • Game scripting features are attack surfaces — even in single-player contexts, crafted save files or shared maps can contain malicious command block configurations that execute when loaded
  • The threat model matters — this game mod requires loading a crafted asset to exploit, but the fix is still critical because players share maps and save files

How Orbis AppSec Detected This

  • Source: User text input from the in-game command block dialog (the text variable populated from the text input UI at the "run-javascript" option)
  • Sink: eval("try{ " + text + "} catch(e) { Vars.ui.showText(error,e)}") at scripts/CommandBlock.js:211
  • Missing control: No input sanitization, validation, allowlisting, or scope isolation between user input and code execution
  • CWE: CWE-95 (Improper Neutralization of Directives in Dynamically Evaluated Code / Eval Injection)
  • Fix: Replaced eval() with string concatenation with new Function(text) wrapped in a native try/catch block, eliminating the injection vector and providing scope isolation

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

This vulnerability in CommandBlock.js demonstrates why eval() with string concatenation remains one of the most dangerous patterns in JavaScript — even in contexts like game scripting where code execution is intentional. The critical distinction is between controlled code execution (isolated scope, proper error handling, no injection amplification) and uncontrolled code execution (local scope access, breakable control flow, string injection). By switching to new Function() with a native try/catch, the fix preserves the intended "run JavaScript" feature while eliminating the security amplification that made the original implementation exploitable beyond its intended purpose.

If you maintain game mods, scripting engines, or any code that dynamically executes user input, audit your use of eval() today — and consider whether new Function(), a sandboxed iframe, or a purpose-built interpreter would better serve your security requirements.

References

Frequently Asked Questions

What is eval injection?

Eval injection occurs when untrusted input is passed to an eval() function, allowing an attacker to execute arbitrary code in the application's runtime context with full access to local scope variables and application state.

How do you prevent eval injection in JavaScript?

Avoid eval() entirely when possible. If dynamic code execution is required, use the Function constructor for scope isolation, implement input validation/allowlisting, or use a sandboxed execution environment like a Web Worker or iframe sandbox.

What CWE is eval injection?

CWE-95 (Improper Neutralization of Directives in Dynamically Evaluated Code, also known as 'Eval Injection'). Related entries include CWE-94 (Improper Control of Generation of Code) and CWE-79 (Cross-site Scripting).

Is replacing eval() with new Function() enough to prevent code injection?

new Function() provides better scope isolation (it doesn't access local variables) and eliminates string concatenation injection, but it still executes arbitrary code. For full protection, input validation, allowlisting, or sandboxing should also be applied depending on the threat model.

Can static analysis detect eval injection?

Yes, static analysis tools like Semgrep, ESLint (no-eval rule), and specialized security scanners can detect direct eval() usage and flag cases where user input flows into eval statements. Pattern-based rules are highly effective for this vulnerability class.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #15

Related Articles

critical

How Denial of Service via gzip bomb happens in Node.js tar and how to fix it

A critical Denial of Service vulnerability (CVE-2026-59873) was discovered in the node-tar package where attackers could craft malicious gzip archives that expand to consume all available system resources. This vulnerability affected version 7.5.15 of the tar package and was fixed by upgrading to version 7.5.19. The fix protects applications from resource exhaustion attacks when processing untrusted archive files.

high

How HTTP Transport Hijacking via Prototype Pollution happens in Node.js axios and how to fix it

CVE-2026-42033 is a high-severity prototype pollution vulnerability in axios versions prior to 1.15.1 that could allow attackers to hijack HTTP transport configuration through malicious input. This vulnerability affects any Node.js application using vulnerable axios versions to make HTTP requests. The fix involves upgrading axios to version 1.15.1, which patches the prototype pollution flaw and prevents transport layer attacks.

critical

How hardcoded API key exposure happens in JavaScript and how to fix it

A critical security vulnerability was discovered in `js/douban.js` where the Douban API key (`0ac44ae016490db2204ce0a042db2916`) was hardcoded directly in client-side JavaScript. This exposed the API credential to any user who could view the page source or use browser developer tools. The fix removed the hardcoded key, preventing unauthorized API access and potential abuse.

critical

How buffer overflow in Intel SGX enclave ECALLs happens in C and how to fix it

A critical buffer overflow vulnerability was discovered in Intel SGX enclave functions `ecall_encrypt_data` and `ecall_decrypt_data` in `backend/sgx/enclave/enclave.c`. The functions performed memory operations without validating that the provided buffer lengths matched the actual allocated buffer sizes, allowing an attacker controlling the untrusted application to trigger heap corruption within the secure enclave by passing oversized length parameters.

critical

How Server-Side Request Forgery happens in Node.js server.js and how to fix it

A Server-Side Request Forgery (SSRF) vulnerability was discovered in `server.js` and `worker.js`, where user-supplied `config` URL parameters were passed directly to `fetchWithAuth()` without any validation. This allowed attackers to force the application to make requests to internal network addresses, cloud metadata endpoints like `169.254.169.254`, or `file://` URIs. The fix introduces an `isAllowedUrl()` allowlist function that rejects private IP ranges, loopback addresses, and non-HTTP(S) pr

critical

How command injection via execSync() happens in Node.js CLI tools and how to fix it

A critical command injection vulnerability was discovered in `packages/core/bin/cli.js` where the `copyToClipboard` function used `execSync()` with shell command strings. Combined with insufficient filename sanitization in the cache functions, an attacker could inject arbitrary shell commands through malicious repository data containing shell metacharacters. The fix replaces `execSync()` with `execFileSync()` and tightens input sanitization on cache file paths.