Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,18 @@ test/
- **Debug protection** (`debugProtection` option, `--no-debug-protection` CLI to override presets): Multi-layered anti-debugger system with 3 independent detection layers: (1) built-in prototype integrity (Object/Array/JSON method monkey-patch detection), (2) environment analysis (--inspect flags, stack traces), (3) function integrity self-verification (FNV-1a checksum). No eval(), new Function(), `debugger` statements, or console.log() calls — fully Chrome extension CSP/TrustedScript compatible. Escalating response requires 3 consecutive detection rounds before acting: silent bytecode corruption → cache/constants wipe → total bytecode annihilation + infinite busy loop. Uses recursive setTimeout with jitter and `.unref()` for Node.js compatibility. Error messages mimic native V8 messages. Implemented in `ruamvm/builders/debug-protection.ts`.
- **Browser support**: `browser-entry.ts` provides a clean ESM entry point re-exporting `obfuscateCode`, presets, and types. `browser-worker.ts` implements a Web Worker message protocol for playground use (`{id, code, options}` → `{id, result, elapsed}`). `browser-crypto-shim.ts` polyfills Node.js `crypto.randomBytes()` using Web Crypto API.

### Anti-AI-decompilation hardening (root-cause framing)

These layers attack the four primitives a generalized AI decompiler uses (P1 recognize, P2 lift-and-run, P3 observe-at-a-tap, P4 oracle), rather than patching transcript-specific steps. Honest ceiling: a single self-contained deterministic file (or a co-resident multi-file bundle) cannot be made impossible to reconstruct — client-executable code is client-observable. On-device levers RAISE WORK FACTOR and DESTROY automatability/transferability; the only cryptographic ("force a human, offline") lever is an off-device secret. See `docs/superpowers/specs/2026-06-30-anti-ai-decompilation-design.md`.

- **Source-map / cleartext-leak gate** (`source-map-gate.ts`): directory/bundle mode copies the whole input tree, so `.js.map` files (carrying `sourcesContent` = original source) shipped verbatim next to obfuscated output. `gateSourceMaps()` deletes `*.map` and strips any surviving `sourceMappingURL` annotation. On by default in `runVmObfuscation` + CLI directory mode; `--keep-source-maps` / `keepSourceMaps` opts out.
- **Cross-file cohort tangle (Layer 1)** (`compiler/cohort.ts`, `obfuscateBundle()`): a directory/bundle is a *cohort*. An order-independent digest of every file's source, folded with a per-build cohort seed (`createCohort().digestAll()`), is XOR-folded into each file's key anchor via the same seam `integrityHash` uses (build `keyAnchor ^= term`; runtime `_ka ^= term` in `assembler.ts`). Build==runtime symmetric across all seeds. Raises the static-correlation unit to the whole bundle and makes identical source diverge per bundle/build. Honest scope: **work-factor** (an attacker holding the whole bundle can recompute the term), not cryptographic. Only takes effect when `rollingCipher` is on. `obfuscateBundle(files, options)` is the additive public API; `obfuscateCode`/`obfuscateFile` signatures are unchanged (optional trailing `cohort` param). CLI directory mode, `runVmObfuscation`, and `index` batch route through it.
- **Off-device key binding** (`externalKeyBinding` option, `--external-key-value`/`--external-key-accessor` CLI): the one lever that crosses from work-factor to cryptographic hardness. Build folds `fnv1a(secretValue)` into the key anchor; runtime recomputes `fnv1a` over a host-supplied secret read from a dotted accessor path (`buildExternalKeyFold` in `ruamvm/builders/external-key.ts`) and folds it identically. The secret VALUE is never embedded — only the accessor path — so the artifact cannot derive its key without the correct runtime secret ⇒ offline AI reconstruction is denied. Opt-in only (no preset), auto-enables `rollingCipher`. Accessor emitted as plain member access (no `eval`, CSP-safe); read wrapped in try/catch so an absent secret yields garbage, not a crash. Invalid accessor throws at build (dev gate). **Trade-off: breaks offline use of the protected asset.**
- **Hole-tolerant slow-path dispatch** (`ruamvm/builders/interpreter.ts`): slow-path handler resolution is `_ht[PH] | 0`. A wrong decryption key yields out-of-range physical opcodes; previously `_ht[oob]` was `undefined` → throw / invalid lookup → an *oracle* telling the attacker the key is wrong (transcript step 15). `| 0` coerces `undefined` → handler 0 (plausible-but-wrong dispatch) and is **identity on every valid handler index**, so legitimate execution is byte-for-byte unchanged across all seeds. Matches the decode-cache path, where `Int32Array` storage already coerces `undefined` → 0.
- **Per-unit key salt** (always-on under rolling cipher): every compiled unit carries a per-unit salt (`deriveSeed(seed, "unitSalt:"+id)`) in its serialized header (a `u32` after `registerCount`, read into `U.us`), folded into the key anchor at build (`serializeUnit`) and mirrored by `rcDeriveKey`'s `k ^= u.us` at runtime. Closes same-metadata key reuse: two units with identical `instr/reg/param/const` counts previously derived the SAME key → identical position keystream → cross-unit decryption. Salts both the instruction keystream AND the string-constant key (both derive from the single `rcDeriveKey(U)` path), so symmetry is by construction.
- **Cross-file runtime link (Layer 2)** (`crossFileLinking` via `obfuscateBundle(files, options, link)`, CLI `--link-provider`/`--link-consumers`): the literal "a file cannot run without the others". A declared *provider* file writes a per-cohort secret to a shared global at load (char-code, CSP-safe, prepended on every return path); each *consumer* folds `fnv1a(that secret)` into its key (reusing the externalKey fold). **STRICT — no fallback**: a consumer cannot decrypt without its provider present-and-earlier in the same realm. Caller owns the co-residence contract (prove-or-don't-link by explicit declaration; paths matched by exact-or-basename). Honest scope: still work-factor vs an attacker holding the whole bundle, but delivers the literal cross-file dependency. Default-OFF, opt-in. Implemented in `compiler/cohort.ts` (`CohortLink`/`providerLink`/`consumerLink`) + `transform.ts` (`buildProviderWrite`).
- **Decode impurity** (`decodeImpurity` option, `--decode-impurity` CLI): the strongest on-device anti-agentic lever. Each instruction's decryption keystream folds in an accumulator chained from every PRIOR decrypted instruction (linear decode order), removing the position cipher's random-access property — an attacker cannot decrypt instruction K without first decrypting 0..K-1, forcing full sequential per-unit simulation. Build==runtime symmetric because the decode cache materializes the stream in a single linear forward pass at BOTH build and runtime (independent of execution path). Build-side `rollingEncryptChained`; runtime chained keystream in the cache materialization (`interpreter.ts`). **Gated to cache-active builds; INCOMPATIBLE with `incrementalCipher`/`opcodeMutation`/`observationResistance`/`vmShielding` (each disables/bypasses the cache) — combining throws at build time.** Every encoded unit passes a **MANDATORY build-time self-equality gate** (`assertChainedDecryptInverts`) — a divergence is a loud build failure, never a silent miscompile. Opt-in, no preset, auto-enables `rollingCipher`.

## Design Principles

These principles govern all contributions to Ruam. They are non-negotiable.
Expand Down
Loading
Loading