diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c02da87..89c14a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ concurrency: jobs: build-and-test: - name: Build, Test & Stats + name: Build & Test runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -42,25 +42,13 @@ jobs: - run: bun run build:lib - run: bun run typecheck - run: bun run test - - run: bun run stats - - - uses: actions/upload-artifact@v4 - if: github.ref == 'refs/heads/main' - with: - name: stats - path: packages/ruam/stats.json - - # Publish stats.json to GitHub Pages alongside the Next.js site - - name: Copy stats.json into site - if: github.ref == 'refs/heads/main' - run: cp packages/ruam/stats.json apps/web/public/stats.json # Build browser worker bundle for the playground - name: Build browser worker bundle if: github.ref == 'refs/heads/main' run: bun run build:worker - - name: Build site with stats + - name: Build site if: github.ref == 'refs/heads/main' run: bun run build:web diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 63548f6..249bbc5 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -6,8 +6,6 @@ on: paths: - 'apps/web/**' - '.github/workflows/deploy.yml' - paths-ignore: - - 'packages/ruam/**' concurrency: group: deploy-pages @@ -36,12 +34,9 @@ jobs: - run: bun install - # Build ruam + collect stats so the site always includes stats.json + # Build and verify Ruam before bundling the playground. - run: bun run build:lib - run: bun run test - - run: bun run stats - - name: Copy stats.json into site - run: cp packages/ruam/stats.json apps/web/public/stats.json # Build browser worker bundle for the playground - name: Build browser worker bundle diff --git a/README.md b/README.md index 69269a5..e00739d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@
+:::::::.. ... ::: :::. . : ;;;;``;;;; ;; ;;; ;;`;; ;;,. ;;; [[[,/[[[' [[' [[[ ,[[ '[[, [[[[, ,[[[[, @@ -7,566 +7,331 @@ 888b "88bo,88 .d888 888 888,888 Y88" 888o MMMM "W" "YmmMMMM"" YMM ""` MMM M' "MMM-Virtualization-Based (VM) JavaScript Obfuscator
+ Isogloss execution protection for guarded JavaScript relations -Compiles JavaScript (JS) functions into custom bytecode executed by an embedded virtual machine.
+
-No deobfuscator exists for RuamVM bytecode** NOTE: Fable 5 is able to deobfuscate single-file Ruam code.+ Ruam replaces explicitly bounded, side-effect-free source relations with + contextual BPRF realizations while keeping deployment claims honest. +
--
-
+
+
+
+
- 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. -
- -Source JS → Transformed JS - (still JS)
-Source JS → Custom Bytecode - + Embedded VM
-| 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:ruamvm
-
-Requires 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 -I
-
-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" },
-});
+Configured targets fail closed. If a function is named in `regionDomains` but
+its selected expression cannot be lowered, protection stops with a structured
+error instead of shipping the configured relation unchanged.
-Not every function needs virtualization. Use comment mode to protect only what matters:
/* ruam:vm */
-function sensitiveLogic() {
- // → compiled to bytecode
-}
+The default `holographic-local` profile is **client-complete**. Everything
+needed to execute the protected relation is present in the client. Its
+contextual realizations and fragmentation can raise the cost of static and
+dynamic analysis, but they do not create secrecy and do not establish a
+hardness lower bound. An attacker with unrestricted execution and
+instrumentation can ultimately reconstruct local behavior.
-function publicHelper() {
- // → untouched, no overhead
-}
-
-ruam app.js -m comment
-
-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.
- -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
-});
-
-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/**")
+Ruam's owner-planning learnability checks are also non-claims. They compute
+constructive exact black-box attack **upper bounds** and reject a planned region
+when a known attack is strictly cheaper than the configured threshold. Passing
+that owner-side gate does not prove that attacks require the threshold number
+of queries.
-Other:
- --debug-logging Inject verbose VM trace logging
- -I, --interactive Launch interactive configuration wizard
- -h, --help Show help
- -v, --version Show version
+Stronger deployment profiles are architectural deployments, not local switches:
-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",
-});
+Custodied, private, and TEE deployments must be assembled by the owner-side
+product planner using real boundary evidence and supported capabilities. The
+source API will not invent a suspension point, silently graft a remote call
+onto synchronous code, or embed a complete local relation for outage or
+development fallback.
-obfuscateFile(inputPath, outputPath, options?)Reads a file, obfuscates it, and writes the result. Returns a Promise<void>.
import { obfuscateFile } from "ruam";
+Ruam requires Node.js 18 or newer and ships as ESM.
-await obfuscateFile("src/app.js", "dist/app.js", { preset: "max" });
+## Quick start
-runVmObfuscation(directory, config?)Obfuscates all matching files in a directory. Returns a Promise<void>.
import { runVmObfuscation } from "ruam";
+```js
+import { protectCode } from "ruam";
+
+const source = `
+ function price(quantity, unitPrice) {
+ return (quantity * unitPrice) + (quantity - 1);
+ }
+`;
+
+const build = protectCode(source, {
+ isogloss: {
+ profile: "holographic-local",
+ },
+ targetMode: "root",
+ regionDomains: {
+ price: {
+ quantity: { type: "number", min: 1, max: 100 },
+ unitPrice: { type: "number", min: 1, max: 10_000 },
+ },
+ },
+});
+
+console.log(build.code);
+console.log(build.stats.clientCompleteness); // "complete"
+console.log(build.stats.hardnessLowerBound); // null
+console.log(build.diagnostics);
+```
+
+The configured numeric bounds are inclusive safe-integer ranges. The generated
+function rejects any `quantity` or `unitPrice` outside those ranges.
+
+### Protect only annotated functions
+
+Set `targetMode: "comment"` and use the exact marker
+`/* ruam:isogloss */` immediately before the function:
+
+```js
+import { protectCode } from "ruam";
+
+const source = `
+ /* ruam:isogloss */
+ function sensitiveScore(x, y) {
+ return (x * y) + (x - 3);
+ }
+
+ function publicLabel(value) {
+ return String(value);
+ }
+`;
+
+const build = protectCode(source, {
+ targetMode: "comment",
+ regionDomains: {
+ sensitiveScore: {
+ x: { type: "number", min: 1, max: 20 },
+ y: { type: "number", min: 2, max: 30 },
+ },
+ },
+});
+```
+
+Only `sensitiveScore` is considered. The marked function still must satisfy all
+purity, shape, type, and domain checks.
+
+### Protect one file
+
+`protectFile` writes the generated code and returns the same build result as
+`protectCode`:
+
+```js
+import { protectFile } from "ruam";
+
+const build = await protectFile("src/pricing.js", "dist/pricing.js", {
+ targetMode: "root",
+ regionDomains: {
+ price: {
+ quantity: { type: "number", min: 1, max: 100 },
+ unitPrice: { type: "number", min: 1, max: 10_000 },
+ },
+ },
+});
-await runVmObfuscation("dist/", {
- include: ["**/*.js"],
+console.log(build.stats.protectedRegionCount);
+```
+
+Omit the output path to overwrite the input file.
+
+### Protect a directory
+
+`runProtection` processes matching files and returns one build result per file:
+
+```js
+import { runProtection } from "ruam";
+
+const results = await runProtection("dist", {
+ include: ["score.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:
- -super)if, for, for-in, for-of, while, do-while, switch, labeled statements)try/catch/finally, throw)...args in calls, arrays, and object literals)let/const with proper TDZ, per-iteration bindings)yield, yield*), async generatorsUse --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
-
-"type": "module")