Dev.to Security 🔐 Cybersecurity 👁 0 📖 5 min read

Which JavaScript files you should actually obfuscate (and which you should leave alone)

Most teams that add JavaScript protection start in the wrong place: they run an obfuscator over the entire production bundle and hope for the best. The result is usually a slower app, a larger download, a few broken edge

Most teams that add JavaScript protection start in the wrong place: they run an obfuscator over the entire production bundle and hope for the best. The result is usually a slower app, a larger download, a few broken edge cases, and no real increase in the cost of stealing the one function that actually mattered.

This article is a decision framework for what to protect. The rule is simple. Protect the smallest set of your own code that would hurt you if it were copied or casually inspected. Leave everything else alone.

First, the honest limit. Anything that runs in the browser can be observed. Obfuscation and VM protection raise the cost of reading and copying that code. They do not make it secret, and they do not replace a server for authoritative decisions, payments, or real secrets. If a rule must be enforced, enforce it on the server and treat client protection as a delay, not a lock.

Start by inventorying what you ship

Open the production dist/ (or the equivalent) and list the JavaScript that actually goes out:

find dist -name "*.js" -not -name "*.map" | xargs wc -c | sort -n

You will typically see three groups:

  • Vendor chunks: React, Vue, a router, a date library, analytics, a UI kit.
  • Application UI: layouts, presentational components, style-driven code.
  • Application logic: API clients, pricing, license or entitlement checks, game rules, proprietary calculations, offline algorithms.

Only the third group is a candidate for serious protection. The first two are almost never worth it.

Leave vendor code alone

There is no reason to obfuscate node_modules. The source is already public. Transforming it:

  • inflates the chunk and the CPU cost on every page load,
  • can break libraries that depend on Function.prototype.toString or fn.name,
  • wastes build time,
  • and gives an attacker nothing they could not get from npm.

Split vendor into its own chunk and exclude it from the protection step. If a tool has no exclude list, it is the wrong tool for a production pipeline.

Leave presentational UI alone

Layout components, form chrome, routing shells, and CSS-in-JS helpers are not a trade secret. Heavy transforms — control-flow flattening, string-array rotation, VM compilation — add measurable runtime cost. Spending that cost on a <Header /> is a gift to no one except the people who will file a "the app feels janky" ticket.

Minify this code. That is enough. Minification already strips comments and shortens names; it is a size optimization, not a protection layer, and you should not pretend otherwise.

A useful test: if a competitor could reimplement the file in an afternoon by looking at the running UI, protecting it is theater.

Protect your own application logic — selectively

This is the middle tier: the modules you wrote that implement real behavior, but that are not uniquely valuable on their own. Examples:

  • a non-trivial client for your own API, including retry and error mapping,
  • feature-flag evaluation that is already mirrored on the server,
  • data transformations that would be inconvenient but not catastrophic to leak,
  • game or editor glue that is not the core simulation.

Standard obfuscation is usually enough here: identifier renaming, string encoding, simple control-flow changes. The goal is to stop a casual copy-paste and to make a quick read in DevTools unrewarding. You do not need a VM for this tier.

Watch for code that will break under renaming:

  • anything that reads fn.name or Function.prototype.toString,
  • string-based property access that assumes a literal key the obfuscator will rename,
  • stack-trace parsers that match original function names,
  • debug hooks you forgot to strip.

Exclude or rewrite those modules rather than turning protection off globally.

Reserve the strongest protection for a short list

The top tier is the code that, if copied, actually damages you: a proprietary scoring or pricing engine that ships in the client because it has to run offline; an offline license verifier; a unique simulation or solver; a watermark or domain-lock that must not be a five-line if.

For this tier, standard obfuscation is a weak barrier. Public deobfuscators undo a lot of renaming and string encoding. VM-based protection is the stronger option: selected functions are compiled to a custom bytecode and executed by a small interpreter, so the original control flow is not sitting in the file for a tool to pattern-match.

VEILVM is a browser-based JSVMP workspace you can point at one extracted function to see what that looks like before you commit a whole pipeline to it. Interpreted code is slower, which is exactly why the list should be short. Extract the sensitive function, protect that function, and leave the rest of the chunk on the cheaper path.

A practical size rule: if you cannot name the functions you are protecting in one sentence, you are protecting too much.

What not to "protect" because it should not be in the client

Some files look like they belong in the top tier and should not be in the bundle at all:

  • API keys, signing secrets, or service tokens,
  • the authoritative price or discount calculation,
  • license grants that a patched client could fake,
  • anything you would be upset to see pasted into a public gist.

Move these to the server. Protecting a secret that already ships to the browser is not a strategy. Protection can hide the shape of leftover client logic; it cannot unsay a credential.

Do not apply this to store-distributed browser extensions either. Extension stores have their own review and policy constraints, and "obfuscate the extension to hide behavior" is the wrong problem to solve.

A working default

If you want a default policy you can write into a build config:

  1. Vendor chunks: minify only, never obfuscate.
  2. UI and presentational app code: minify only.
  3. Ordinary application logic you wrote: standard obfuscation, with an exclude list for known-fragile patterns.
  4. A named list of sensitive functions (start with three or fewer): VM protection, tested against the production bundle.
  5. Secrets and authoritative rules: not in the client.

Then measure. Record bundle size and a couple of key interaction timings before and after. If a protected function is on the hot path of every frame or every keystroke, it does not belong in the VM tier — move the work, or accept a cheaper transform.

Checklist before you ship

  • [ ] Vendor is a separate chunk and is excluded from protection.
  • [ ] You can name the functions in the strongest tier.
  • [ ] Production source maps are not shipped (private upload to your crash tracker is fine).
  • [ ] End-to-end tests run against the protected build, not the dev server.
  • [ ] No API keys or signing secrets remain in any protected or unprotected chunk.

If you want to try the VM layer on a single function, VEILVM runs in the browser with no sign-up: 3 free runs a day, up to 5 KB per file (resets at 00:00 UTC). A free account raises the input limit to 30 KB. Pro is $9/month or $79/year and adds an API so the protection step can run in CI. Standard requests are processed in memory and are not written to logs. Start at https://vm.ldvmp.com.

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.