obfus.link
Obfuscators

Scope-aware identifier mangling with reversible mapping and Devcore mode

Obfuscate variable, function, and class identifiers in JavaScript, TypeScript, or Python with scope-targeted partial mangling, reversible mappings for round-trip workflows, and themed Devcore vocabularies for CTF and code-as-art.

The Var Name Mangler uses AST parsing to mangle identifiers within a targeted scope in JavaScript, TypeScript, or Python — outer-scope references stay intact. Reverse mode restores names from a mapping for round-trip workflows. Devcore strategy applies cyberpunk, noir, vaporwave, or glitch themed vocabularies.

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.

Afterdevcore 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 output

The 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

code

string

Source code to mangle

language

'javascript' | 'typescript' | 'python'

Source language — drives the AST parser

strategy

'hex' | 'phonetic' | 'random' | 'incremental' | 'devcore'

Naming strategy for new identifiers

preserveExports

boolean

true

Keep exported names readable (export function X, export class Y)

devcoreTheme

'cyberpunk' | 'noir' | 'vaporwave' | 'glitch'

for devcore strategy

40-word themed vocabulary

mode

'mangle' | 'reverse'

'mangle'

Forward obfuscation or restoration

mappingInput

Record<string, string>

for reverse mode

The mapping from a prior mangle call

scopeTarget

string

Function or class name to scope the mangle to

mangleTypes

boolean

false

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

hex

_0x4a2f, _0xb1c3

Concise, looks technical, easy to skim past

phonetic

_grafok_, _trasil_, _bolovan_

Pronounceable nonsense — easier for humans to grep / discuss

random

_ek32a, _b8e2c, _po51l

Maximum visual entropy

incremental

a, b, c, ..., aa, ab

Shortest output — for code-size optimisation use cases

devcore (+ theme)

_neon_, _chrome_, _void_ (cyberpunk)

Themed obfuscation for CTF / education / code-as-art

Error codes

Code

When it fires

Recovery

INPUT_EMPTY

code empty

Provide non-empty source

INPUT_TOO_LARGE

code exceeds 500KB

Mangle one module at a time — this tool isn't built for whole-codebase passes

INPUT_MALFORMED

AST parser failed (syntax error)

Fix the syntax in the source; the mangler needs valid input

INPUT_INVALID_TYPE

Reverse mode without mappingInput, or mapping doesn't cover identifiers in the input

Pass the exact mapping returned by the original mangle call

UNSUPPORTED_LANGUAGE

Language outside {javascript, typescript, python}

The supported set is fixed; use a dedicated mangler for other languages

SECURITY_VIOLATION

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.

Try it now

Var Name Mangler

Obfuscate variable names in JS, TS, and Python with Devcore mode

FAQ

Frequently asked questions

Is this a replacement for a minifier?

No. Minifiers like Terser, esbuild, and swc do mangling as part of a larger compression pipeline (dead-code elimination, constant folding, scope hoisting). This tool only renames identifiers. Use a real minifier for production bundles; use this tool for partial obfuscation, CTF challenges, or round-trip workflows.

What does scope-targeted mode do that global mode doesn't?

Global mode mangles every identifier in the file. Scope-targeted mode mangles only identifiers declared inside a named function or class — outer-scope references stay readable. Use scope-targeted when you want to obfuscate a proprietary helper inside an otherwise open-source library.

Can I round-trip mangle and unmangle?

Yes. mode: 'mangle' returns a mapping. Save the mapping, send the mangled code through an untrusted intermediary, get a response, then call mode: 'reverse' with mappingInput set to the saved mapping. The mapping is keyed by request id in production pipelines.

What's in the Devcore themes?

Each theme is a 40-word vocabulary. Cyberpunk: _neon_, _chrome_, _void_, _ghost_, _syn_, _hex_, _null_, _wire_, _blade_, _jack_, .... Noir: _shadow_, _alibi_, _smoke_, _caine_, _grift_, .... Vaporwave: _lo-fi_, _static_, _palm_, _dusk_, _glow_, .... Glitch: Unicode-rich (_∆_, _◯_, _xx_, _yy_, _zz_).

Does it work on minified code?

Yes if the minified code is still parseable JavaScript / TypeScript. The AST parser doesn't care about formatting. The output retains the minified style. Useful for renaming identifiers inside already-minified output without un-minifying first.