:::::::.. ... ::: :::. . : ;;;;``;;;; ;; ;;; ;;`;; ;;,. ;;; [[[,/[[[' [[' [[[ ,[[ '[[, [[[[, ,[[[[, $$$$$$c $$ $$$c$$$cc$$$c $$$$$$$$"$$$ 888b "88bo,88 .d888 888 888,888 Y88" 888o MMMM "W" "YmmMMMM"" YMM ""` MMM M' "MMM
Virtualization-Based (VM) JavaScript Obfuscator
Compiles JavaScript (JS) functions into custom bytecode executed by an embedded virtual machine.
Obfuscation does not guarantee secrecy or prevent reconstruction by an attacker who controls execution.
Installation · Quick Start · How It Works · Presets · API
The experimental offline regional compiler is available through explicit research APIs and ruam --regional-research. Its protection strength is unqualified; it does not replace the production VM. See the implementation and evidence and the selected design.
Most JavaScript obfuscators apply surface-level transformations — renaming variables, encoding strings, inserting dead code. A motivated attacker can undo these with off-the-shelf tools, logic analysis, or by patching functions at runtime.
Ruam compiles eligible JavaScript functions into a custom bytecode instruction set and embeds a virtual machine to execute them. Code outside the selected compilation scope can remain ordinary JavaScript. The artifact contains what it needs to run offline, so an attacker can inspect the interpreter, observe execution, and attempt to reconstruct equivalent logic.
Source JS → Transformed JS (still JS)
- Same language, same semantics
- AST-reversible transformations
- Automated deobfuscation tools exist
Source JS → Custom Bytecode + Embedded VM
- Selected function bodies become bytecode
- Attackers can analyze the VM or infer equivalent behavior
- Recovery resistance must be measured against concrete attacks
| 300+ opcode open-source ISA | Full-coverage instruction set spanning 26 categories — stack, arithmetic, bitwise, comparison, control flow, property access, scoping, calls, classes, iterators, destructuring, async/await, generators, and more. |
| Per-build polymorphism | Every build produces structurally unique output. No two builds share the same encoding, identifier names, or internal structure — even from the same source. |
| Multi-layer encryption | Bytecode is encrypted with multiple independent layers. Keys are derived implicitly from the output's own structure — no key material appears in plaintext. |
| Anti-tamper binding | The VM's decryption logic is entangled with its own source. Modifying the interpreter to add logging or breakpoints corrupts all decryption — the bytecode becomes unrecoverable. |
| Runtime metamorphism | The instruction set mutates during execution. The same opcode byte maps to different operations at different points in the program. Static disassembly produces incorrect results. |
| Optimizing compiler | Multi-tier optimization pipeline minimizes the performance cost of virtualization. Fused instructions, register promotion, and inline operations keep overhead competitive for a JS-in-JS interpreter. |
| Thousands of tests | Comprehensive test suite covering core JS semantics, stress/edge cases, security properties, and integration scenarios — including randomized fuzz tests. |
npm install ruam@npm:ruamvm
# or
bun add ruam@npm:ruamvm
# or
pnpm add ruam@npm:ruamvm
# or
yarn add ruam@npm:ruamvmRequires Node.js >= 18. Ships as ESM only.
Ruam is published to npm under the name ruamvm (the registry rejects ruam as too similar to raf/rax/read). Installing via the ruam@npm:ruamvm alias keeps imports and CLI invocations as ruam everywhere. The CLI binary is also installed as ruamvm if you prefer.
# Obfuscate a file in-place
ruam app.js
# Obfuscate to a new file
ruam app.js -o app.obf.js
# Obfuscate a directory with medium preset
ruam dist/ --preset medium
# Maximum protection
ruam dist/ --preset max
# Interactive wizard (or just run `ruam` with no args)
ruam -Iimport { obfuscateCode, obfuscateFile, runVmObfuscation } from "ruam";
// Synchronous — obfuscate a code string
const result = obfuscateCode('function hello() { return "world"; }');
// Async — obfuscate a file
await obfuscateFile("src/app.js", "dist/app.js");
// Async — obfuscate a directory with options
await runVmObfuscation("dist/", {
include: ["**/*.js"],
exclude: ["**/node_modules/**"],
options: { preset: "max" },
});Not every function needs virtualization. Use comment mode to protect only what matters:
/* ruam:vm */
function sensitiveLogic() {
// → compiled to bytecode
}
function publicHelper() {
// → untouched, no overhead
}ruam app.js -m commentRuam applies multiple independent protection layers that compound the difficulty of reverse engineering. Each layer forces an attacker to solve a distinct problem before they can make progress on the next.
| Layer | Description |
|---|---|
| Virtualization | Selected function bodies are compiled to a custom bytecode ISA. VM analysis, runtime instrumentation and behavioral inference remain possible recovery approaches. |
| Polymorphic encoding | The instruction encoding, identifiers, and internal structure are randomized per build. Reversing one build provides zero reusable knowledge about any other build. |
| Instruction encryption | Every instruction is individually encrypted. The key is derived from properties of the output itself — no key material is stored in plaintext. Sequential decryption is required; you cannot jump into the middle of a bytecode stream. |
| Integrity binding | The decryption process is entangled with the VM interpreter's own source. Modifying the VM in any way (adding logging, setting breakpoints, patching behavior) silently corrupts all decryption. |
| VM shielding | Each function can receive its own isolated micro-interpreter with unique encoding, encryption keys, and internal structure. Reversing one function's interpreter does not help with any other. |
| String encoding | All string constants in the bytecode are independently encrypted. No plaintext strings survive compilation — not variable names, property keys, or literal values. |
| Anti-debug | Multi-layered runtime detection with escalating response. No eval(), new Function(), debugger statements, or console calls — fully compatible with strict CSP environments including Chrome extensions. |
| Arithmetic obfuscation | Arithmetic and bitwise operations within the interpreter are replaced with mathematically equivalent but opaque compound expressions, making the interpreter logic harder to follow. |
| String atomization | All string literals in the interpreter — property names, method names, internal labels — are replaced with encoded table lookups. Zero hardcoded strings survive in the output. |
| Block permutation | Bytecode basic blocks are randomly reordered within each compiled function. Control flow is preserved through explicit jumps. The bytecode stream no longer reflects the original program structure. |
| Key scattering | Cryptographic key materials are split into fragments distributed across multiple closure scopes in the output. Recovering any single key requires tracing the entire scope chain. |
Additional hardening options include dead bytecode injection, stack value encryption, decoy opcode handlers, handler fragmentation, runtime opcode mutation, and polymorphic string decoding — all configurable independently or via presets.
Three built-in presets provide escalating protection. Explicit options always override preset values.
| Preset | What's enabled | Use case |
|---|---|---|
low |
VM compilation only | Development, debugging, basic IP protection |
medium |
+ identifier renaming, bytecode encryption, instruction encryption, decoy & dynamic opcodes, string atomization, key scattering | Production — balanced protection and size |
max |
Everything — all encryption layers, VM shielding, debug protection, integrity binding, arithmetic obfuscation, dead code, stack encoding, block permutation, string atomization, key scattering | High-value targets — maximum protection |
ruam dist/ --preset maxobfuscateCode(source, {
preset: "medium",
debugProtection: true, // override: add debug protection to medium
});Virtualization inherently adds overhead — this is the tradeoff for protection that surface-level transforms cannot provide. Ruam's multi-tier optimization pipeline minimizes the cost.
Typical overhead: ~38–45x native speed on compute-heavy benchmarks, competitive for a pure JS-in-JS interpreter.
Use selective obfuscation (-m comment) to protect only sensitive functions and leave hot paths running as native JS.
ruam <input> [options]
Presets:
--preset <name> Apply a preset: low, medium, max
Output:
-o, --output <path> Output file or directory (default: overwrite input)
Compilation:
-m, --mode <mode> Target mode: "root" (default) or "comment"
-e, --encrypt Enable bytecode encryption
-p, --preprocess Rename all identifiers before compilation
Security:
-d, --debug-protection Enable anti-debugger protection
--no-debug-protection Disable anti-debugger (overrides preset)
--rolling-cipher Enable instruction encryption
--integrity-binding Bind decryption to interpreter integrity
--vm-shielding Per-function isolated micro-interpreters
Hardening:
--dynamic-opcodes Filter unused opcodes from the interpreter
--decoy-opcodes Add fake opcode handlers
--dead-code Inject dead bytecode sequences
--stack-encoding Encrypt values on the VM stack
--mba Arithmetic obfuscation (mixed boolean arithmetic)
--handler-fragmentation Split handler logic into interleaved fragments
--string-atomization Replace interpreter strings with encoded lookups
--polymorphic-decoder Per-build randomized string decoding chain
--scattered-keys Fragment key materials across closure scopes
--block-permutation Shuffle bytecode basic block order
--opcode-mutation Insert runtime handler table mutations
Environment:
--target <env> Target environment: node, browser (default), browser-extension
File Selection:
--include <glob> File glob for directory mode (default: "**/*.js")
--exclude <glob> Exclude glob (default: "**/node_modules/**")
Other:
--debug-logging Inject verbose VM trace logging
-I, --interactive Launch interactive configuration wizard
-h, --help Show help
-v, --version Show versionSynchronously obfuscates a JavaScript source string. Returns the obfuscated code as a string.
import { obfuscateCode } from "ruam";
const output = obfuscateCode(source, {
preset: "medium",
targetMode: "root",
});Reads a file, obfuscates it, and writes the result. Returns a Promise<void>.
import { obfuscateFile } from "ruam";
await obfuscateFile("src/app.js", "dist/app.js", { preset: "max" });Obfuscates all matching files in a directory. Returns a Promise<void>.
import { runVmObfuscation } from "ruam";
await runVmObfuscation("dist/", {
include: ["**/*.js"],
exclude: ["**/node_modules/**"],
options: { preset: "medium" },
});| Option | Type | Default | Description |
|---|---|---|---|
preset |
"low" | "medium" | "max" |
— | Apply a preset configuration |
targetMode |
"root" | "comment" |
"root" |
"root": all top-level functions. "comment": only /* ruam:vm */ annotated |
threshold |
number |
1.0 |
Probability (0–1) that an eligible function is compiled |
target |
"node" | "browser" | "browser-extension" |
"browser" |
Target execution environment |
preprocessIdentifiers |
boolean |
false |
Rename all local identifiers before compilation |
encryptBytecode |
boolean |
false |
Encrypt bytecode using an environment fingerprint key |
rollingCipher |
boolean |
false |
Per-instruction encryption with implicit key derivation |
integrityBinding |
boolean |
false |
Bind decryption to interpreter source integrity (auto-enables rollingCipher) |
vmShielding |
boolean |
false |
Per-function micro-interpreters with unique encoding (auto-enables rollingCipher) |
debugProtection |
boolean |
false |
Multi-layered anti-debugger with escalating response |
dynamicOpcodes |
boolean |
false |
Filter unused opcodes from the interpreter |
decoyOpcodes |
boolean |
false |
Add fake opcode handlers to the interpreter |
deadCodeInjection |
boolean |
false |
Inject unreachable bytecode sequences |
stackEncoding |
boolean |
false |
Encrypt values on the VM stack at runtime |
mixedBooleanArithmetic |
boolean |
false |
Replace arithmetic/bitwise ops with opaque MBA expressions |
handlerFragmentation |
boolean |
false |
Split opcode handlers into interleaved fragments |
stringAtomization |
boolean |
false |
Replace all interpreter string literals with encoded table lookups (auto-enables polymorphicDecoder) |
polymorphicDecoder |
boolean |
false |
Per-build randomized byte-operation chain for string decoding |
scatteredKeys |
boolean |
false |
Fragment and scatter key materials across closure scopes |
blockPermutation |
boolean |
false |
Shuffle bytecode basic block order within compiled functions |
opcodeMutation |
boolean |
false |
Insert runtime handler table mutations (auto-enables rollingCipher) |
debugLogging |
boolean |
false |
Inject verbose trace logging into the interpreter |
Ruam compiles the full range of modern JavaScript:
- Functions (declarations, expressions, arrows, generators, async, async generators)
- Classes (inheritance, constructors, methods, getters/setters, computed properties, static members,
super) - Control flow (
if,for,for-in,for-of,while,do-while,switch, labeled statements) - Exception handling (
try/catch/finally,throw) - Destructuring (array and object patterns, defaults, rest elements, nested)
- Spread/rest (
...argsin calls, arrays, and object literals) - Closures and lexical scoping (
let/constwith proper TDZ, per-iteration bindings) - Async/await, generators (
yield,yield*), async generators - Template literals, tagged templates, optional chaining, nullish coalescing
- Computed property names, shorthand properties/methods, symbol keys
Use --target to optimize output for your deployment environment:
| Target | Description |
|---|---|
browser |
Plain <script> tags. Default. |
node |
Node.js (CJS or ESM modules). |
browser-extension |
Chrome extension MAIN world content scripts. Wraps output to avoid TrustedScript CSP errors. |
ruam content-script.js --target browser-extension --preset max- Node.js >= 18
- ESM (
"type": "module")