Skip to main content
Variable and symbol renaming is the foundational obfuscation technique for Lua and Luau. It strips every human-readable name from your source code and replaces it with meaningless identifiers that carry no semantic value. When applied correctly, renaming makes reverse engineering significantly harder because an attacker can no longer infer purpose from names like calculateDamage or playerInventory. This guide covers what renaming can and cannot obscure, demonstrates multiple naming strategies, and explains the critical scope analysis rules you must follow to avoid breaking your script.

What Renaming Obfuscates

A thorough rename pass targets every identifier that you control:
  • Local variables: local score = 0 becomes local _a = 0
  • Function names: function addPoints(p) becomes function _b(_c)
  • Parameters: including self in method definitions
  • Upvalues: local variables captured by nested functions
  • Table keys: string keys in tables you define and access within the same script
  • Module names: local Utils = require("Utils") becomes local _d = require("Utils") (the require string itself is a separate concern handled by string encoding)

What Renaming Does NOT Obscure

Renaming is powerful but has hard limits. You must not rename identifiers that belong to the runtime environment:
  • Standard library names: print, table, math, string, pcall, error, pairs, ipairs, tonumber, tostring, type, assert, collectgarbage, getmetatable, setmetatable, rawget, rawset, rawequal, load, loadfile, dofile
  • Globals injected by the host: in Roblox, names like game, workspace, script, Instance, Vector3, CFrame, Enum are provided by the engine and must remain intact
  • String literals inside require paths: require("ReplicatedStorage.Module") must keep the exact path unless you also rewrite the module structure
  • Metamethod names: __index, __newindex, __add, __tostring, and others are looked up by exact string match at runtime
Renaming a standard library name or a Roblox global will cause runtime errors. Your obfuscator must maintain a whitelist of protected names.

Before and After Example

Here is a readable game score tracker and its fully renamed equivalent. Both scripts behave identically at runtime.
Notice that standard names like setmetatable, table.insert, and math.max remain untouched. The renamed version is functionally identical but offers no semantic clues.

Naming Strategies

The choice of replacement names affects both readability for an attacker and the size of your output. Here are the most common strategies from simplest to most aggressive.

Sequential Integers

The simplest approach assigns _0, _1, _2, _3 in declaration order.
This is easy to implement and produces small output, but an attacker can quickly spot patterns. Sequential names also make it obvious which variables were declared first.

Hex-Style Names

Using hexadecimal prefixes adds visual noise without increasing size much.
The _0x prefix tricks the eye into reading numbers rather than identifiers, slowing down manual analysis slightly.

Unicode Homoglyphs

Lua 5.3 and Luau support UTF-8 identifiers. You can substitute visually similar characters from other alphabets.
Other useful homoglyphs include:
  • Е (Cyrillic Ye, U+0415) vs E (Latin E, U+0045)
  • р (Cyrillic Er, U+0440) vs p (Latin P, U+0070)
  • ѕ (Cyrillic Dze, U+0455) vs s (Latin S, U+0073)
You can also embed zero-width spaces (U+200B) or zero-width non-joiners (U+200C) inside identifiers. These characters are invisible in most editors but are significant to the Lua lexer.
Homoglyphs can break in environments with strict font rendering or when copied through channels that strip non-ASCII characters. Test your output in the target runtime before deploying.

Keyword Lookalikes

Names that resemble reserved words create visual confusion without being syntax errors.
Because _if is not the literal token if, the parser accepts it. An attacker scanning quickly may misread these as control flow keywords.

Random Alphanumeric Strings

The strongest naming strategy generates 8-16 character random strings.
Random names have no pattern to exploit. The tradeoff is larger script size and slightly slower parsing in some interpreters.

Collision Attacks

You can intentionally reuse the same short name at different lexical scopes to confuse readers. A human analyst may assume _a in two places refers to the same entity.
Each _a is a distinct local bound to its own scope. This is legal Lua but mentally taxing to trace.

Scope Analysis

Renaming must be scope-aware. Lua’s lexical scoping means that a local variable is visible from its declaration point to the end of the block, including inside nested functions that capture it as an upvalue. A naive global search-and-replace will break your script.

Lexical Scoping in Lua

The two x variables are completely separate storage locations. A renamer must track which x each reference binds to.

Broken Rename Example

Here is what happens when a renamer ignores scope:
The incorrect renamer collapsed both playerCount locals into _a. This changes behavior: getCount now captures the inner _a if the closure is created after setCount runs, or worse, it may reference the wrong slot depending on bytecode layout.

Correct Scope-Aware Rename

A correct renamer maintains a stack of scope tables. Each scope maps original names to generated names. When resolving a reference, the renamer walks from innermost scope to outermost, stopping at the first match.

Upvalue Chains

When a nested function references a local from an outer function, Lua creates an upvalue. Renaming must propagate the new name to both the declaration site and every capture site.
Both occurrences of count become _b. If the renamer treated the inner function as a separate scope without upvalue linking, it might generate _c for the inner reference, breaking the closure.

Parameter Renaming

Function parameters are local variables that exist only inside the function body. Each parameter gets an independent generated name.

Vararg Limitation

The vararg parameter ... cannot be renamed. It is a fixed token in the Lua grammar. You can still obscure its usage by immediately binding it to a local:

Method Parameter Self

In Lua method syntax function obj:method(a, b), the colon implicitly adds self as the first parameter. You can safely rename self to any valid identifier, but you must preserve the colon syntax or convert it to dot syntax explicitly.
Do not rename self inside a colon-defined method while leaving other references to self unchanged. Either rename all occurrences consistently or leave self untouched.

Table Key Renaming

Tables with string keys are ubiquitous in Lua. Renaming these keys requires updating both the definition site and every access site.

Dot Notation vs Bracket Notation

Both config.maxPlayers and config["maxPlayers""] must be rewritten to use the new key name. A static analysis pass must track which table a key belongs to.

Full Table Key Example

Keys passed to external APIs must never be renamed. For example, Instance:FindFirstChild("Humanoid") must keep the string "Humanoid" because the Roblox engine looks up that exact name. Only rename keys for tables that are entirely internal to your script.

Upvalue and Closure Renaming

Closures are functions that capture variables from their enclosing scope. Renaming a captured variable requires renaming it at the point of declaration and at every point of use inside the closure.
The upvalue _c is declared in createMultiplier and referenced inside the anonymous returned function. Both sites use _c.

Luau-Specific Notes

Luau extends Lua with a static type system. Type annotations are not present at runtime, but they appear in source code and may leak information.

Strip Type Annotations Before Renaming

Type annotations like : number reveal intent. Strip them before the rename pass, or the attacker sees playerHealth: number even after renaming the variable.

Exported Module Members

In Roblox, a module script typically returns a table of functions that other scripts require. The keys of that returned table are the public API.
If you rename MyModule.doSomething to _a._b, any consumer script that calls require(script.Parent.MyModule).doSomething() will break. You have three options:
  1. Do not rename exported keys: keep doSomething intact
  2. Obfuscate the consumer too: rename the access site in every script that requires the module
  3. Use an indirection table: return a table with original keys that map to renamed internal functions
_ENV is a special upvalue introduced in Lua 5.2 that holds the global environment table. In Lua 5.1, the equivalent concept is the implicit global table accessed by unqualified names. You should not rename _ENV for several reasons.First, _ENV is looked up by the compiler as a reserved upvalue name. While it is technically a regular local in Lua 5.2+, the compiler treats it specially during chunk compilation. Replacing it with a generated name may confuse the bytecode loader or produce unexpected global resolution behavior.Second, many scripts rely on _ENV being accessible for sandboxing patterns:
If you rename _ENV here, the sandboxing intent becomes harder to read, but the runtime behavior depends on the exact Lua version. In Luau, _ENV does not exist in the same form; globals are resolved differently. The safest rule is: leave _ENV untouched unless you are writing a custom bytecode obfuscator that also rewrites the global table reference mechanism.
Metamethod names like __index, __newindex, __add, __sub, __mul, __div, __mod, __pow, __unm, __concat, __len, __eq, __lt, __le, __tostring, __gc, __mode, __call, and __metatable are looked up by exact string match at runtime. If you rename __index to _a, the metatable mechanism will not find it and your object-oriented code will break.There is one safe exception: if you implement __index as a function table rather than a string key, you can rename the function itself because the string key __index remains intact:
If you use __index to proxy property lookups to an external API (like Roblox Instance properties), the property names inside the proxy function must also remain unchanged:
The rule is simple: metamethod string keys are sacred. Function values assigned to those keys can be renamed, but the keys themselves cannot.