1. Insight
Insight
The problem this article addresses and why it matters.
Why minifier-style mangling is the wrong tool for IP protection
The standard JavaScript minifier (Terser, esbuild, swc) renames identifiers as a side-effect of compression: myImportantFunction becomes a, userId becomes b. The output is unreadable to a casual reader but trivially reversible by anyone who knows the source structure or who runs the minified code through a Source-Map-aware debugger. Minifier mangling is for bytes, not for secrets.
A growing class of use cases needs something different. CTF challenge authors want obfuscated code that's culturally legible — themed variable names that hint at the puzzle's narrative without giving the answer away. Open-source library maintainers want to protect specific helper functions from being trivially understood by a competitor while keeping the public API readable. Agents writing code for untrusted pipelines want a reversible mapping so they can deobfuscate the upstream system's response after it bounces through an external service.
Plain minifier output covers none of these cases. They need a tool that thinks about which identifiers to mangle, what style to mangle them into, and how to get them back.
Why scope-awareness changes the game
The naive approach to identifier mangling is global find-and-replace: every occurrence of userId becomes _neon_0. That breaks the moment you have two functions that both declare a local userId — the mangle conflates them. Worse: it breaks when a function takes userId from a closure or a destructured outer scope, because the global replace touches the outer reference too.
The tool in this article uses AST parsing to understand JavaScript / TypeScript / Python scope rules. With scopeTarget set to a specific function or class name, only identifiers declared inside that scope get mangled. Outer-scope references retain their original names. The result is partial obfuscation that compiles, runs, and preserves the public API exactly.
What this article delivers
Three modes walked end-to-end: standard mangling with strategy variants (hex, phonetic, random, incremental, devcore), scope-targeted mangling for partial obfuscation, and reverse mode for round-trip workflows. We cover the four devcore themes (cyberpunk, noir, vaporwave, glitch), the AST limitations on dynamic property access, and the cases where this tool isn't a substitute for a real minifier.
2. Intent
Intent
What you will be able to do after reading.
By the end of this article you will be able to:
- Mangle identifiers in JavaScript, TypeScript, or Python source with one of five strategies — hex, phonetic, random, incremental, or devcore-themed
- Use scope-targeted mode to obfuscate only the identifiers inside a named function or class, leaving outer-scope references intact
- Reverse the mangle by passing the mapping back through the tool — round-trip workflows for agent pipelines that send code through untrusted intermediaries
- Pick a devcore theme (cyberpunk, noir, vaporwave, glitch) for code-as-art use cases where the obfuscation should look intentional
- Identify the dynamic-access patterns the tool cannot mangle safely (
obj[someName],eval, runtime-constructed access)
The Examples section walks through partial obfuscation of a real helper function, a devcore-themed CTF challenge, and a reverse-mode round trip.
3. Examples
Examples
Annotated code and worked scenarios.
Before / after: scope-targeted obfuscation
You're publishing an open-source library and want to protect your internal calculateBillingMetrics helper without obfuscating the rest of the file:
Before:
export function processBilling(account: Account): Bill {
return calculateBillingMetrics(account);
}
function calculateBillingMetrics(account: Account): Bill {
const baseRate = account.tier === 'pro' ? 0.12 : 0.08;
const usageOverage = account.usage - account.included;
const overageRate = usageOverage > 0 ? baseRate * 1.5 : 0;
return {
base: baseRate * account.included,
overage: overageRate * usageOverage,
total: baseRate * account.included + overageRate * usageOverage,
};
}After:
varNameMangler({
code,
language: 'typescript',
strategy: 'phonetic',
preserveExports: true,
scopeTarget: 'calculateBillingMetrics',
mode: 'mangle',
});Output:
export function processBilling(account: Account): Bill {
return calculateBillingMetrics(account);
}
function calculateBillingMetrics(account: Account): Bill {
const _grafok_ = account.tier === 'pro' ? 0.12 : 0.08;
const _trasil_ = account.usage - account.included;
const _bolovan_ = _trasil_ > 0 ? _grafok_ * 1.5 : 0;
return {
base: _grafok_ * account.included,
overage: _bolovan_ * _trasil_,
total: _grafok_ * account.included + _bolovan_ * _trasil_,
};
}calculateBillingMetrics keeps its public name (it's referenced from processBilling — outer scope). The Account type, account parameter, and Bill return type stay readable. The internal locals (baseRate, usageOverage, overageRate) are mangled because they're declared inside the targeted scope. The numeric coefficients (0.12, 0.08, 1.5) are still visible — this isn't constant folding, it's identifier mangling.
The mapping is in the response:
// mapping: {
// baseRate: '_grafok_',
// usageOverage: '_trasil_',
// overageRate: '_bolovan_',
// }
// scopeReport: {
// scopeName: 'calculateBillingMetrics',
// identifiersInScope: 3,
// identifiersMangled: 3,
// identifiersPreserved: 5, // account, tier, usage, included, ...
// }Before / after: devcore theme for a CTF challenge
You're writing a CTF challenge. The puzzle is reverse-engineering a JavaScript decoder. You want the code to look obfuscated but culturally on-theme:
Before — plain random strategy:
function _ek32a(b8e2) {
const _po51l = b8e2 ^ 0x42;
return String.fromCharCode(_po51l);
}Functional but boring. Players see "obfuscated JS" and move on.
After — devcore with cyberpunk theme:
varNameMangler({
code,
language: 'javascript',
strategy: 'devcore',
devcoreTheme: 'cyberpunk',
mode: 'mangle',
});
// function _neon_(_void_) {
// const _chrome_ = _void_ ^ 0x42;
// return String.fromCharCode(_chrome_);
// }Now the obfuscation is part of the puzzle's identity. The cyberpunk vocabulary (_neon_, _chrome_, _void_, _ghost_, _syn_, _hex_, _null_, _wire_, _blade_, _jack_, …) is 40 words deep — enough to obfuscate a non-trivial program without collisions.
Other themes:
noir—_shadow_,_alibi_,_smoke_,_caine_,_grift_, …vaporwave—_lo-fi_,_static_,_palm_,_dusk_,_glow_, …glitch—_∆_,_◯_,_xx_,_yy_,_zz_, …
Before / after: round-trip via mapping
Reverse mode is the structural feature most other obfuscators don't ship. You mangle, send the mangled code through an external service, get the response back, then deobfuscate:
// Step 1: mangle before sending out
const mangled = varNameMangler({
code: originalSource,
language: 'typescript',
strategy: 'hex',
preserveExports: false, // protect the API surface too
mode: 'mangle',
});
// mangled.result.mangled → the obfuscated source you ship
// mangled.result.mapping → save this, keyed by request id
// ... external service processes the mangled source ...
// Step 3: reverse the mangle on the response
const restored = varNameMangler({
code: externalServiceOutput,
language: 'typescript',
strategy: 'hex',
preserveExports: false,
mode: 'reverse',
mappingInput: mangled.result.mapping,
});
// restored.result.restored → the deobfuscated outputThe pattern is widely useful for agent pipelines where intellectual property (function names, internal variable names) shouldn't leak to a third-party service the agent is composing with.
When humans use this
CTF authors are the most common use — themed obfuscation makes the puzzle's vibe match its difficulty. Library maintainers occasionally use scope-targeted mode for proprietary helpers in otherwise open-source code. The most underused mode is round-trip: send code to a LLM-powered review service via mangled identifiers, get the review back, unmangle for human reading.
When agents use this
Three production patterns where mangling adds real value:
- Agent-to-agent code sharing through untrusted pipelines. An orchestrator agent passes generated code to a specialised review agent at a third-party provider. Mangling the identifiers prevents the provider from learning the originator's internal naming conventions (which often leak business logic), while the round-trip mapping lets the originator restore meaningful names on the response.
- Open-source code generation with proprietary cores. An agent producing a customer-facing SDK generates open-source-style code for the public API and mangles a small set of internal helper functions. The result is a working library where the proprietary parts are deliberately opaque.
- CTF / education generation. An agent generating exercises for a security training course uses devcore-themed mangling to set tone — the exercises feel like part of a unified curriculum rather than a random collection of obfuscated snippets.
Edge cases
Dynamic property access
obj[varName] where varName is a string variable cannot be safely mangled — the AST can't statically determine which property is being accessed. The tool surfaces these with a warning per occurrence; the surrounding identifiers are mangled but the dynamic access site itself is left untouched. Same for eval, Function(...), and new Function(...) — those are anti-patterns the tool refuses to mangle around.
TypeScript type-only identifiers
Type aliases, interface names, and generic parameters can be mangled if they're declared inside the targeted scope. They're not mangled by default because the resulting code is much harder to read for the maintainer; pass mangleTypes: true to opt in explicitly.
Python scoping quirks
Python's scoping rules differ from JS — list comprehensions create scopes, nonlocal declarations cross scopes. The tool handles both; the scopeReport shows which identifiers crossed which scope boundary.
Preserved exports + scope targeting
When preserveExports: true and scopeTarget are both set, the export name is preserved even when the export is declared inside the targeted scope. This is the partial-obfuscation case: ship export function helperA with a mangled body, keeping helperA callable from outside the library.
4. Documentation
Documentation
Reference signatures, edge cases, and lookup tables.
Input parameters
Field | Type | Required | Default | Description |
|---|---|---|---|---|
|
| ✓ | — | Source code to mangle |
|
| ✓ | — | Source language — drives the AST parser |
|
| ✓ | — | Naming strategy for new identifiers |
|
| ✗ |
| Keep exported names readable ( |
|
| for devcore strategy | — | 40-word themed vocabulary |
|
| ✗ |
| Forward obfuscation or restoration |
|
| for reverse mode | — | The mapping from a prior mangle call |
|
| ✗ | — | Function or class name to scope the mangle to |
|
| ✗ |
| TypeScript only — also mangle type aliases and interface names |
Output shape
{
mangled?: string; // forward mode
restored?: string; // reverse mode
mapping: Record<string, string>; // original → mangled (or reverse)
scopeReport?: {
scopeName: string;
identifiersInScope: number;
identifiersMangled: number;
identifiersPreserved: number; // outer-scope refs left untouched
};
warnings: Array<{
type: string; // 'dynamic_access' | 'eval' | 'preserved_export'
location: { line: number; column: number };
message: string;
}>;
}Strategy comparison
Strategy | Sample output | Use case |
|---|---|---|
|
| Concise, looks technical, easy to skim past |
|
| Pronounceable nonsense — easier for humans to grep / discuss |
|
| Maximum visual entropy |
|
| Shortest output — for code-size optimisation use cases |
|
| Themed obfuscation for CTF / education / code-as-art |
Error codes
Code | When it fires | Recovery |
|---|---|---|
|
| Provide non-empty source |
|
| Mangle one module at a time — this tool isn't built for whole-codebase passes |
| AST parser failed (syntax error) | Fix the syntax in the source; the mangler needs valid input |
| Reverse mode without | Pass the exact mapping returned by the original mangle call |
| Language outside | The supported set is fixed; use a dedicated mangler for other languages |
| Prompt-injection pattern detected in code or in mapping | Sanitise the source upstream; do not retry |
When NOT to use this tool
Don't use it as a security boundary. Mangled identifiers slow a casual reader but don't stop a motivated reverse-engineer. For real IP protection, ship code through a server-side runtime (the source never leaves the server) and expose only an API surface.
Don't use it as a minifier. Terser, esbuild, and swc do mangling as part of a larger compression pipeline (dead-code elimination, constant folding, scope hoisting) that this tool doesn't perform. The output is identifier-renamed source, not minified bytes.
Don't use the devcore themes in production code. They make the obfuscation memorable, which means anyone reading the result can grep for the theme words to recover the structure quickly.
Performance notes
Typical execution: 50-200ms depending on AST size. Scope-targeted mangling adds 20-40ms for the scope walker. Devcore strategy is the slowest because the vocabulary lookup runs per-identifier. The tool is deterministic per (code, strategy, scopeTarget, devcoreTheme) tuple — same input produces the same mangled output and mapping. REST responses are Edge-Cache eligible.
The Python AST parser depends on having a Python-compatible grammar embedded in the tool — it's about 60% of the surface of ast.parse from CPython 3.11. Edge constructs (walrus operator inside complex contexts, structural pattern matching) may produce INPUT_MALFORMED rather than parsing. Falls back gracefully.