Documentation

Protect your Luau scripts

LionFuscator turns readable Luau/Roblox source into a protected file that runs identically but is extremely hard to read, edit, or steal. It layers a bytecode virtual machine, an encrypted VM loader, runtime-tied keys, string/constant encryption, control-flow flattening, anti-tamper & anti-hook, and per-build metamorphism - the same class of protection as Luraph.

The golden rule: the engine only parses and transforms your code - it never executes it, so uploading is safe. As with any VM-based protector, run the protected file once in your executor before shipping.

Quick start

  1. Open the Dashboard and paste your Luau source, or drag a .lua file onto the drop zone.
  2. Pick a preset (start with max - the default - or performance for FPS-sensitive game code).
  3. Press Obfuscate. You get the protected file in seconds, with a validation check that confirms the output is structurally sound.
  4. Copy or Download the result and test it in your executor.

Every build spends one credit. Each build is also metamorphic - obfuscating the same file twice produces two structurally different outputs, so signatures never transfer.

Presets

A preset is a bundle of protection layers tuned for a trade-off between strength and runtime cost. Stronger presets add more layers; the only layer with meaningful per-frame cost is the per-function VM (see FPS).

PresetBest forWhat it adds
lightQuick, minimal protection Variable renaming + string encryption.
balancedGeneral scripts + number obfuscation, control-flow flattening, property & builtin encryption, 2 decoders.
performanceFPS-critical game code Every cheap-but-strong layer (encrypted strings, flattening, opaque predicates, property/builtin encryption, anti-tamper, anti-hook, metamorphism) but no per-instruction VM.
max default Strong protection, most scripts Everything in performance + expression VM + property/method encryption + 3 decoder & 3 VM pools + runtime-tied keys.
ultraMaximum protection max + function-body virtualization + encrypted VM loader + 8 decoders + anti-hook probe.
ultra compiles whole function bodies to bytecode for the strongest protection we offer. Virtualized code runs through an interpreter, so for code that runs every frame you can keep the rest of ultra and lift just the hot paths out of the VM with the FPS controls below, or build with the performance preset.

Protection layers

Bytecode virtualization

Function bodies are lifted into a custom virtual machine with a per-build instruction set. The source-level logic is not present in the output - only virtualized code is.

Encrypted VM loader

The interpreter itself ships encrypted and is reconstructed at load. The output file on disk is not directly readable execution material.

Runtime-tied keys

Decryption keys are derived at load time from runtime state - never baked as literals. Recovering any string or constant requires reproducing the live derivation.

String & constant encryption

Every string, number, property name and table key is encrypted and split across independent decode paths. Nothing readable survives a scan.

Control-flow flattening

Straight-line bodies become dispatch-loop state machines with shuffled states and predicate guards - the original source order is not recoverable.

Anti-tamper & anti-hook

The engine binds decryption to the integrity of the runtime it loads into. If the environment has been modified, output resolves to unusable data - there is no clean intermediate to walk away with. Layered cross-checks make partial tampering equally unproductive.

Property/method encryption

Property access and method calls are rewritten through encrypted lookups. No Roblox API name is readable in the output.

Metamorphic output

Structural details are randomized per build. Tools and signatures tuned to one output do not transfer to the next.

FPS & performance

Most layers (string/const encryption, flattening, property encryption, anti-tamper/hook, metamorphism) are essentially free at runtime. The one layer with real per-frame cost is the per-function bytecode VM in ultra. Three ways to keep FPS high:

  • Use the performance preset. It keeps every cheap-but-strong layer and drops only the per-instruction VM - ideal for scripts that run every frame.
  • Auto-native (on by default in ultra). Trivial pass-through functions - a lone call or a lone return of a call/field/value, like one-line UI handlers and thin per-frame dispatchers - are kept native automatically. Zero security loss: those bodies have no logic the VM could hide.
  • Hot event callbacks stay native (on by default in ultra). Functions connected to per-frame and per-input signals - RenderStepped, Heartbeat, Stepped, InputChanged, BindToRenderStep, and the rest - run native, because they fire many times per second and are the main FPS cost. Every other layer (renaming, string/const/property encryption, flattening, anti-tamper/hook) still applies to them, and every function they call stays fully virtualized. Turn off Native render / input callbacks in the dashboard's VM & FPS card to virtualize these too.
  • Max FPS switch. The dashboard's “Max FPS (disable function VM)” checkbox turns the per-function VM off for the whole file while keeping every other ultra layer.

Keeping specific functions native

For surgical control, annotate a hot function with a --@native comment on the line directly above it. That body stays native (no VM overhead) while everything else is still fully virtualized - the best of both worlds for aimbots, render loops, and tight per-frame code.

--@native
local function onRenderStep(dt)   -- runs native, no VM overhead
    updateAimbot(dt)
end

To exclude a whole block at once - a UI library, a rendering module, anything that runs hot and holds no secrets - wrap it between --@native-begin and --@native-end. Every function inside the region runs native; everything outside stays fully virtualized. Regions can nest, and an unclosed --@native-begin runs to the end of the file.

--@native-begin
local UI = {}
function UI.draw(...)   ... end   -- native
function UI.layout(...) ... end   -- native
--@native-end

local function validateKey(k)     -- still fully virtualized
    ...
end

Only the per-function bytecode VM is skipped in a native region - renaming, string / constant / property encryption, control-flow flattening, anti-tamper and anti-hook still apply, and every function the region calls stays virtualized.

Compression ultra only

Ultra stages its interpreter through an encrypted VM loader. That one-time loader blob can be LZW-compressed to cut file size and load-time download - applied under the encryption (compress-then-encrypt), so it never exposes plaintext and never changes in-game FPS.

LevelDictionaryNotes
0-Off (default)
112-bitLightest; smallest runtime table
214-bitBalanced
316-bitBest ratio (helps most on large scripts)

Header / watermark Premium

Every build carries a short branded header comment by default. Its contents are metamorphic - no two builds share the same layout, so it is not a fixed signature.

On a paid plan you can, from the dashboard, choose:

  • Branded (default) - the standard LionFuscator header.
  • No header - completely clean, unbranded output.
  • Custom note - your own one-line comment (e.g. © MyGame 2026). It's sanitized so it can never break out of the comment.

Credits & plans

Access is metered by credits: every successful build spends one. Subscription plans refill to their monthly allotment on a rolling 30-day window; the one-time Now token adds a prepaid batch that never expires.

PlanCreditsNotes
Free20 / monthEvery preset, no card needed.
Now1 (one-time)Single-use token, no subscription.
Basic500 / monthMonthly subscription.
Premium1000 / month+ Premium header settings.
Commercial2000 / month+ obfuscation API.
EnterpriseCustomCurated limits & support.

Your remaining balance shows in the header and on the dashboard. When you run out, upgrade or grab a token. A build that fails validation does not spend a credit - you're only charged for a validated output. The Commercial and Enterprise plans also include the obfuscation API and let you manage subscriptions (cancel, invoices, payment method) from the dashboard's Billing card.

API Commercial & Enterprise

Obfuscate scripts programmatically over HTTPS. Same engine, same presets, same output as the dashboard - spends one credit per successful build.

Create a key

On the dashboard, open the API keys card, give the key a label, and hit Create API key. The full key is shown once - save it somewhere safe (a password manager or your bot's secrets store). We only store its prefix and a one-way hash; if you lose it, revoke it and create a new one.

Key format: lion_<32 alphanumeric chars>.

Endpoint

POST https://lionfuscator.onrender.com/api/v1/obfuscate
Authorization: Bearer lion_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json

{
  "source":         "local x = 1\nprint(x)\n",
  "preset":         "ultra",       // light | balanced | performance | max | ultra
  "name":           "myscript.lua",
  "seed":           null,          // optional, deterministic build seed
  "compress_level": 0,             // 0-3, ultra only
  "disable_func_vm": false,        // FPS switch, ultra only
  "auto_native":    true,          // ultra only, on by default
  "watermark":      null           // null=branded, ""=none, string=custom
}

Response

{
  "ok": true,
  "job_id": 42,
  "output": "-- ... protected Lua ...",
  "validated": true,
  "warnings": [],
  "preset": "ultra",
  "seconds": 1.4,
  "in_bytes": 12345,
  "out_bytes": 62000,
  "growth_pct": 402,
  "credits": 998
}

Errors

HTTPerrorMeaning
401missing_api_key / invalid_api_key No Authorization: Bearer … header, or the key is unknown/revoked.
402out_of_credits Balance is 0 - upgrade or wait for the monthly refill.
403api_not_available Owner's plan was downgraded off Commercial/Enterprise.
413- Source larger than 2 MB.
422- Engine could not parse the source (message names the exception).
429rate_limited Per-key quota exceeded (Commercial 60/min, Enterprise 240/min).

Example - curl

curl -X POST https://lionfuscator.onrender.com/api/v1/obfuscate \
  -H "Authorization: Bearer $LIONFUSCATOR_KEY" \
  -H "Content-Type: application/json" \
  --data @request.json \
  | jq -r '.output' > script.protected.lua

Example - Python

import os, requests

with open('script.lua', encoding='utf-8') as f:
    source = f.read()

resp = requests.post(
    'https://lionfuscator.onrender.com/api/v1/obfuscate',
    headers={'Authorization': f'Bearer {os.environ["LIONFUSCATOR_KEY"]}'},
    json={'source': source, 'preset': 'ultra', 'name': 'script.lua'},
    timeout=120,
)
data = resp.json()
if not data.get('ok'):
    raise RuntimeError(data.get('message') or data.get('error'))
with open('script.protected.lua', 'w', encoding='utf-8') as f:
    f.write(data['output'])
print(f'Built in {data["seconds"]}s, {data["credits"]} credits left.')
Credits & billing. Successful builds spend one credit each; a validation failure is refunded. Revoking a key stops all future requests using it immediately - existing successful jobs stay in your history.

Account & 2FA

Everything account-related lives on the Account page - a link is in the header next to your username. It has:

  • Profile - your plan, credit balance, refill date.
  • Change password - confirms with your current password. New password must be ≥ 8 characters and different from the old one.
  • Change email - confirms with your password. The new address is marked unverified and a verification link is emailed to it.
  • Two-factor authentication (TOTP) - see below.
  • Delete account - permanently removes your account, obfuscation history, saved outputs, and API keys. Requires your password + typing your username to confirm. Cancel any active subscription first from the dashboard's Billing card.

Setting up 2FA

2FA adds a second sign-in factor so a stolen password alone can't log in. LionFuscator uses standard TOTP - the same protocol Google Authenticator, Authy, 1Password, Bitwarden, and every serious password manager already support.

  1. Open Account → Two-factor auth and click Set up 2FA.
  2. Scan the QR code with your authenticator app (or paste the shown secret manually).
  3. Enter the 6-digit code your app now shows to prove enrollment worked - 2FA is only turned on once we've verified a live code, so a mis-scan can't lock you out.
  4. Save your 10 backup codes. Each works once (format abcd-1234) and lets you sign in if you lose your device. You can regenerate them at any time - old codes stop working.

To sign in with 2FA enabled, enter your password as usual, then the 6-digit code (or a backup code) on the next screen. Backup codes are one-shot - using one removes it from your account.

To turn 2FA off, go back to Account → Two-factor auth and confirm with your password. The secret and any unused backup codes are wiped.

Security model

LionFuscator raises the cost of reverse-engineering enormously, but no client-side protection is unbreakable - the machine that runs your script can, in principle, observe it. What LionFuscator guarantees:

  • No readable surface. After max/ultra, no string, number, Roblox property, method name, or global appears in plaintext.
  • Execution-gated recovery. Keys are derived at runtime from a tamper-tainted master, so strings and constants can't be resolved by static analysis alone.
  • Correct by construction. The VM runs with Lua's own semantics and conservatively falls back on anything outside its proven subset - the worst case is "a function isn't virtualized", never a broken script.
  • Metamorphic. Every build differs, so a signature or a script written against one output doesn't transfer to the next.
The anti-hook probe (ultra) can't be verified offline. It's written to be fail-safe (never trips on a clean run), but test it in your target executor before relying on it.

FAQ & troubleshooting

My protected script errors in the executor.

Re-run with the performance preset (no per-function VM) to isolate whether the per-function VM is involved, and confirm the original script runs cleanly first. Always test one build before shipping. If a specific function misbehaves under ultra, mark it --@native or use the Max FPS switch.

"This file is already obfuscated."

You pasted a previously-protected file. Obfuscate the original source, not an output - double-obfuscation isn't supported.

The output is much larger than my input.

Expected - the VM interpreter, decoders and encrypted tables add fixed overhead. Turn Compression on in the dashboard (levels 1-3) to shrink the file. It doesn't change runtime FPS - only the one-time load-time decompress.

Does obfuscation slow my game down?

Only the per-function VM in ultra has per-frame cost. Use performance, auto-native, or --@native for hot paths - everything else is effectively free.

Is my source code stored?

No. We store build metadata and the protected output so you can re-download from history - never your plaintext input.

Ready to protect a script? Open the dashboard →