Skip to content

owengregson/Ruam

:::::::..    ...    :::  :::.     .        :
;;;;``;;;;   ;;     ;;;  ;;`;;    ;;,.    ;;;
 [[[,/[[['  [['     [[[ ,[[ '[[,  [[[[, ,[[[[,
 $$$$$$c    $$      $$$c$$$cc$$$c $$$$$$$$"$$$
 888b "88bo,88    .d888 888   888,888 Y88" 888o
 MMMM   "W"  "YmmMMMM"" YMM   ""` MMM  M'  "MMM

Virtualization-Based JavaScript Obfuscation

Compiles JavaScript functions into custom bytecode executed by an embedded virtual machine.
No deobfuscator exists for RuamVM bytecode.

Node.js >= 18 LGPL-2.1 TypeScript Strict

Tests Passing Avg Size Ratio VM Overhead

Quick Links

Installation ·  Quick Start ·  How It Works ·  Presets ·  API

Why Ruam?

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 takes a fundamentally different approach. It compiles your JavaScript into a custom bytecode instruction set and replaces the original source with a compact virtual machine that executes an unintelligible instruction stream. The original code is destroyed — it does not exist anywhere in the output.

Traditional Obfuscators

Source JS → Transformed JS (still JS)

  • Same language, same semantics
  • AST-reversible transformations
  • Automated deobfuscation tools exist

Ruam

Source JS → Custom Bytecode + Embedded VM

  • Original source is destroyed
  • Must reverse-engineer the VM itself
  • No deobfuscator exists

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.

Installation

NPM COMING SOON

npm install ruam

Quick Start

CLI

# 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 -I

Programmatic API

import { 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" },
});

Selective Obfuscation

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 comment

How It Works

Ruam 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 Original JS is compiled to a custom bytecode ISA. The source code is destroyed — an attacker must reverse-engineer the entire VM to recover any logic.
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.


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 max
obfuscateCode(source, {
  preset: "medium",
  debugProtection: true,  // override: add debug protection to medium
});

Performance

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.


CLI Reference

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 version

API Reference

obfuscateCode(source, options?)

Synchronously obfuscates a JavaScript source string. Returns the obfuscated code as a string.

import { obfuscateCode } from "ruam";

const output = obfuscateCode(source, {
  preset: "medium",
  targetMode: "root",
});

obfuscateFile(inputPath, outputPath, options?)

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" });

runVmObfuscation(directory, config?)

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" },
});

Options

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

Supported Syntax

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 (...args in calls, arrays, and object literals)
  • Closures and lexical scoping (let/const with 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

Target Environments

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

Requirements

  • Node.js >= 18
  • ESM ("type": "module")

License

LGPL-2.1