---
url: https://docs.sunlicense.com/guide/obfuscation.md
---
# Obfuscation

Protect the code you ship. SunLicense obfuscates it on our servers, so it is far harder to read or reuse:

* **Java** — upload a built `.jar` and we run [ProGuard](https://www.guardsquare.com/proguard), [yGuard](https://yworks.github.io/yGuard/) or [Skidfuscator](https://skidfuscator.dev) over it (pick the engine per profile): it renames your classes, fields and methods, and can shrink, repackage or — with Skidfuscator — add control-flow obfuscation and string encryption. You get the obfuscated jar, a **mapping file** (so you can de-obfuscate your own stack traces) and a report.
* **JavaScript and TypeScript** — upload a `.js`, `.mjs`, `.cjs` or `.ts` file, or a project `.zip`, and we run [javascript-obfuscator](https://github.com/javascript-obfuscator/javascript-obfuscator) over it. You get the obfuscated file (or a zip of your project) and a report. ([See below.](#javascript-and-typescript))
* **Python** — upload a `.py` file or a project `.zip`, and we run [python-minifier](https://dflook.github.io/python-minifier/) over every `.py` in it. The output is plain Python 3 that still runs on your buyers' machines; protection is lighter than Java's. ([See below.](#python))
* **Lua / FiveM** — upload a `.lua` file or a zipped FiveM resource, and we run [Prometheus](https://github.com/prometheus-lua/Prometheus) over your client, server and shared scripts while leaving `fxmanifest.lua`, `html/`, `stream/` and data files alone. ([See below.](#lua-and-fivem))

Obfuscation is on the **Lite** and **Standard** [plans](/guide/plans), with a monthly limit shared across languages. It runs as a [task](/guide/imports-and-tasks), so you can close the page and come back — the result waits for you, and files are kept for 7 days.

## Java

Upload a built Java `.jar` and SunLicense runs ProGuard over it: it renames your classes, fields and methods, and can shrink unused code and repackage everything. You get back the obfuscated jar, the ProGuard **mapping file** (so you can de-obfuscate your own stack traces) and a short report.

### Obfuscate a jar

1. Open **Obfuscation** in the sidebar.
2. Pick your language (**Java**) and an obfuscator — **ProGuard** is the recommended, safe default.
3. Drop in your built `.jar` (or a `.zip` of several jars). SunLicense looks at it and tells you what it found — a Paper plugin, a Fabric mod, a library — names the run for you and applies safe settings, so there's usually nothing else to do.
4. *Optional:* open **Advanced options** to change the settings (below), start from a saved profile, or add **library jars** you compile against (the Paper, Velocity or JDA API) — only needed if the obfuscator reports missing classes. Without them it runs with warnings ignored, and the report says so.
5. Choose **Obfuscate** and watch the live log. When it finishes, download the obfuscated jar, the mapping file and the report.

Your past runs are on the page's **History** tab — open one any time to download its results again (links last a few minutes).

### Your plugin still loads

SunLicense detects your jar's **entry points** and keeps their names, so you never have to write keep rules by hand:

* the manifest `Main-Class`;
* `plugin.yml`, `paper-plugin.yml` and `bungee.yml` `main`, and `velocity-plugin.json`;
* Fabric `fabric.mod.json` entrypoints and mixin classes, and Forge/NeoForge `@Mod` classes;
* `META-INF/services` providers, event handlers (`@EventHandler`, `@Subscribe`), enums and serialisable types.

The **SunLicense SDK's license check** is renamed along with the rest of your code — there is no fixed name for an attacker to find — but it still runs, and the per-buyer download placeholders ([product downloads](/guide/downloads)) are kept intact. Classes that hold a placeholder are never string-encrypted, so per-buyer key injection keeps working; a run that would lose a placeholder is refused.

### Engines

Pick the engine in your profile — one engine runs per job. They trade strength against compatibility:

| Engine | Licence | What it does | Good for |
|---|---|---|---|
| **ProGuard** | GPL-2.0 w/ classpath exception | Renames, and can shrink and optimise bytecode. Mature and widely compatible. | The default; plugins, mods, bots and libraries. |
| **yGuard** | MIT | Renames classes, fields and methods only (no string encryption), configured with its own XML. | A lightweight, highly compatible rename, or if you already have a yGuard config. |
| **Skidfuscator** | MIT | Adds control-flow obfuscation and string encryption on top — much harder to read, at some cost in size and compatibility. | The strongest protection, when you can test the result loads. |

::: tip
Stronger obfuscation is more likely to break reflection-heavy plugins. Try **Skidfuscator** with just string encryption first, test that your plugin still loads, then add control-flow obfuscation.
:::

### Settings

The options depend on the engine (the form is generated from each engine's own schema). For **ProGuard**:

| Option | Default | What it does |
|---|---|---|
| **Rename symbols** | on | Rename classes, fields and methods. |
| **Remove unused code** | off | Drop unused classes and members. Off by default, because plugins and mods are loaded by reflection. |
| **Optimise** | off | Run ProGuard's optimisation passes. |
| **Keep line numbers** | on | Keep line-number tables so your stack traces still map to your source. |
| **Keep public API** | off | Keep public and protected members unrenamed — for libraries other code compiles against. |
| **Repackage classes** | off | Move all renamed classes into a single package. |
| **Keep unrenamed** | — | Extra packages, classes or members to leave untouched, such as `com.acme.api.**`. |

**yGuard** offers *Keep public API*, *Keep line numbers* and *Keep unrenamed*. **Skidfuscator** offers *Encrypt strings* and *Obfuscate control flow* (both off by default) and *Leave untouched*.

#### Advanced

Choose **Advanced** to edit the engine's configuration directly, starting from the one your options generate. SunLicense manages the input, output and library jars for you, so anything that names a file is refused with a clear message: ProGuard directives (`-injars`, `-libraryjars`, `-printmapping <file>`, `@file`, dictionaries…), yGuard XML that adds `inoutpair`/`externalclasses`/`logfile` or a DTD/DOCTYPE, and Skidfuscator `libs`/renaming keys, HOCON `include` or file paths.

## JavaScript and TypeScript

Upload a single `.js`, `.mjs`, `.cjs` or `.ts` file, or a `.zip` of your project. For a project, SunLicense obfuscates every `.js`, `.mjs`, `.cjs` and `.ts` file **outside `node_modules` and your build folders** (`build/`, `dist/`, …); everything else, `node_modules` included, is copied into the result unchanged. TypeScript is transpiled to JavaScript first (a plain transpile — no type checking — so the output is `.js`).

Your `node_modules` doesn't need to be in the zip (your buyers run `npm ci`); if it is, keep it under 150 MB or the run is refused.

Your **SunLicense SDK calls** and the per-buyer download placeholders ([product downloads](/guide/downloads)) are always kept readable, and your `import`/`require` statements are left intact, so licensing and injection keep working.

| Option | Default | What it does |
|---|---|---|
| **Strength** | medium | How hard the transforms you turn on push: `low`, `medium` or `high`. |
| **Runs on** | node | `node` for bots, servers and CLIs; `browser` for front-end bundles. |
| **Encode strings** | off | Encode the extracted string literals (base64, or RC4 on high). |
| **Flatten control flow** | off | Rewrite control flow so it's hard to follow. Can slow hot code down. |
| **Inject dead code** | off | Add believable unreachable code. Grows the file. |
| **Self-defending** | off | Break the output if it's reformatted or beautified — don't post-process the file. |
| **Keep exported names** | on | Leave top-level (exported) names so other code can still import them. |
| **Skip files** | — | In a project zip, paths to leave untouched, such as `src/vendor/**`. |

Every obfuscated file is checked with `node --check` before you download it, so a broken transform never ships.

Choose **Advanced** to edit the [javascript-obfuscator options](https://github.com/javascript-obfuscator/javascript-obfuscator#javascript-obfuscator-options) directly as JSON, starting from the one your options generate. SunLicense manages the input, output and source maps, so options that name files (`inputFileName`, `sourceMap…`, and so on) are refused.

## Python

Upload a single `.py` file or a `.zip` of your whole project (a Discord bot, a tool) and SunLicense runs [python-minifier](https://dflook.github.io/python-minifier/) over every `.py` file in it: it shortens the names used inside your functions and removes docstrings, so the code is harder to read. The output is **plain Python 3** — no compiled binaries, no C extensions — so it still runs on your buyers' Windows, macOS and Linux machines on any Python 3 they have.

In a project `.zip`, SunLicense obfuscates only **your** `.py` files. Anything inside a virtualenv, `site-packages`, `__pycache__` or a package's metadata folder is copied untouched, and so are your non-Python files (`requirements.txt`, assets, data). You get back a `.zip` with the same layout, plus a short report.

::: warning Python protection is lighter than Java's
Python ships as source, so obfuscation can only go so far: names are shortened and docstrings removed, but the program's structure stays. It raises the effort to read or copy your code; it does not make it unrecoverable. If you need stronger protection, Java (compiled bytecode + ProGuard) is the better target.
:::

### Settings {#python-settings}

| Option | Default | What it does |
|---|---|---|
| **Rename local names** | on | Rename arguments and variables used inside a function. This is the main protection. |
| **Rename module-level names** | off | Also rename classes, functions and variables at module level. Off by default: it breaks imports between your own modules, and names your framework looks up by string (discord.py cogs, Flask routes). Turn it on only for a single self-contained file. |
| **Remove docstrings** | on | Drop docstrings and other bare literal text. |
| **Remove type annotations** | off | Strip type hints. Off by default: discord.py commands and libraries like pydantic read annotations at runtime, so removing them can break your bot. |
| **Keep names** | — | Names to never rename, one per line (for example `setup`, or a command your framework finds by name). |

Because module-level names and annotations are kept by default, a typical discord.py bot obfuscates and still imports and runs unchanged.

#### Advanced {#python-advanced}

Choose **Advanced** to edit the engine options directly as a JSON object of [python-minifier keyword arguments](https://dflook.github.io/python-minifier/), starting from the one your options generate (for example `{"rename_locals": true, "rename_globals": false}`). python-minifier reads and writes no files, so there is nothing to point at the filesystem; any option that isn't a known python-minifier flag is refused with a clear message.

## Lua and FiveM resources

Choose the **Prometheus (Lua / FiveM)** engine to obfuscate Lua. Upload either a single `.lua` file or a **zipped FiveM resource** (up to 50 MB).

For a resource zip, SunLicense reads your `fxmanifest.lua` (or the legacy `__resource.lua`) and obfuscates only the scripts it lists under `client_scripts`, `server_scripts` and `shared_scripts` — globs like `server/*.lua` are expanded. Everything else is passed through **untouched**:

* the manifest itself, and `files`/`ui_page`/`data_file` entries;
* `html/`, `stream/` and other assets, and any non-Lua file;
* scripts that belong to another resource (`@other-resource/lib.lua`).

Because Prometheus keeps string literals, your **exports, event names and NUI callbacks keep working** — nothing that other resources call by name changes. A single `.lua` upload is obfuscated as-is.

### Settings (Lua)

| Option | What it does |
|---|---|
| **Protection preset** | `Minify`, `Weak`, `Medium` (recommended) or `Strong` — Prometheus' built-in presets. Stronger presets grow and slow the output. |
| **Lua version** | `Lua 5.1` (FiveM and most Lua) or `LuaU` (Roblox; experimental upstream). |
| **Exclusions** | Resource-relative files to leave alone, e.g. `config.lua` or `locales/*.lua`. |

::: warning FiveM and Lua 5.4
FiveM's CfxLua is based on **Lua 5.4**, but Prometheus parses and emits **Lua 5.1** (or LuaU). Most resources are written in 5.1-compatible syntax and work fine. Scripts that use **5.4-only syntax** — integer division `//`, bitwise operators `& | ~ >> <<`, or `<close>`/`<const>` attributes — won't parse; list those files under **Exclusions** so they're shipped unchanged.
:::

Each output file is syntax-checked (compiled without being run) before you get it back, so a broken obfuscation never ships silently.

### Advanced (Lua)

A Prometheus configuration is itself a Lua table — i.e. code — so, for safety, the **Advanced** editor takes a **JSON** form of it, not Lua. You can set the `preset`, `luaVersion`, `prettyPrint`, `exclusions` and an explicit `steps` list (each an allow-listed step `name` with primitive `settings`); SunLicense builds the Lua config from your JSON and **never runs any Lua you write**. Steps or settings outside the allow-list are refused with a clear message.

> Obfuscation for Lua is powered by **Prometheus** — *Based on Prometheus by Elias Oelschner, <https://github.com/prometheus-lua/Prometheus>*, used under the Prometheus License.

## Profiles

Save a configuration as a **profile** to reuse it on later runs. A profile can be linked to a product, and starting a run copies its settings into the task, so editing the profile later never changes a past result.

::: tip
Keep the **mapping file** from each release. If a customer sends you an obfuscated stack trace, ProGuard's `retrace` tool turns it back into your original class and method names using that file.
:::

## Beyond the dashboard

Obfuscation always runs on SunLicense's servers — never on your machine — so you can reach it the same way everywhere.

### Publish an obfuscated build to Downloads

When you upload a release file under [product downloads](/guide/downloads), turn on **Obfuscate before publishing** and (optionally) pick a profile — the default is the product's linked profile. SunLicense obfuscates the upload before it goes live: the release stays **processing** until it succeeds, then the obfuscated file is published with each buyer's license written in, and your original is removed. Buyers only ever get the obfuscated file. If a run fails, the release shows the task log and stays unpublished.

### From the CLI (and CI)

Use the [`sunlicense obfuscate`](/cli/commands#obfuscate) command to obfuscate a build and download the result, with a non-zero exit code on failure so it fits a release step:

```bash
sunlicense obfuscate build/libs/app.jar --profile release -o dist/
```

The language is detected from the file; pass `--engine` or `--profile` to be explicit, `--config <file>` for a raw engine config, and `--json` for scripts. `sunlicense obfuscate profiles` lists your saved profiles.

### From an AI coding agent (MCP)

The CLI's [MCP server](/cli/mcp) exposes `obfuscation_profiles_list`, `obfuscate_file` (it uploads a local file — the file is never shown to the model) and `obfuscation_status`. Connected apps and SunLicense AI get the read-only tools only.

### From the API

A run uses the standard [tasks API](/api/management#tasks-and-obfuscation): create it with `POST /api/v1/obfuscation/runs` (with a `profileId`, or `engine` + `options`/`rawConfig`), upload the input with the task's presigned URL, submit it, follow the live log over the task's SSE stream, and download the outputs. `GET /api/v1/obfuscation/engines` lists the languages, engines and their options; `GET /api/v1/obfuscation/profiles` lists your profiles.
