ObfuscateJS is a multi-step JavaScript obfuscation toolkit designed to transform clean, readable JS into heavily distorted and unreadable code. It uses a layered pipeline of base64 encoding, chunking, string escaping, random declarations, and injected dead code to maximize entropy and minimize human readability. This project is perfect for those who want to experiment with advanced JS obfuscation techniques or protect their scripts from reverse engineering.
| # | Stage | Script | What it does |
|---|---|---|---|
| 1 | base64 |
JsToBase64.py |
Wraps the payload in eval(atob("...")) |
| 2 | chunks |
AtobToChunks.py |
Re-encodes and splits the payload into a random chunk array inside an IIFE |
| 3 | varsplit |
AtobChunkVariableSplitter.py |
Gives every chunk its own randomly named variable |
| 4 | strings |
StringEscaper.py (new) |
Rewrites string literals as random \xHH / \uHHHH escapes |
| 5 | comments |
AddRandomComments.py |
Inserts gibberish comments, especially above functions |
| 6 | declarations |
AddRandomDeclarations.py |
Injects N random unused variable declarations |
| 7 | deadcode |
AddDeadCode.py |
Injects never-executing code caves mined from ./deadcode |
python3 obfuscate.py --file main_payload.js -o obfuscated.js --seed 42 \
--declarations 1500 --caves 420Per-stage byte sizes and entropy are printed as it runs. Useful options:
--skip strings comments # skip stages by name
--seed 42 # fully reproducible output
--string-ratio 0.8 # fraction of strings to escape
--comment-probability 0.15 # per-line comment probability
--deadcode-dir ./deadcode # corpus directory for code caves
python3 JsToBase64.py --file main_payload.js > 2.js;
python3 AtobToChunks.py --file 2.js > 3.js;
python3 AtobChunkVariableSplitter.py --file 3.js > 4.js;
python3 StringEscaper.py --file 4.js > 4b.js;
python3 AddRandomComments.py --file 4b.js > 5.js;
python3 AddRandomDeclarations.py --file 5.js --count 1500 > 6.js;
python3 AddDeadCode.py --file 6.js --caves 420 > 7.js;
cp 7.js replacement.js;
python3 make_html.py --output obf_test_page.html
Every stage accepts --file/-f or --code/-c, optional --output/-o, and --seed for reproducible output.
python3 entropy.py replacement.js --compare main_payload.js # cross-platform
./entropy.sh replacement.js # original bash version
A terminal-style web interface (webui/) runs the entire pipeline client-side
(webui/pipeline.js is a full JS port of the Python stages, with a seeded PRNG for
reproducible output). It includes per-stage toggles, parameter controls, live
byte/entropy stats, an engine toggle (browser / server), and a sandboxed verify
command that executes both the original and the obfuscated payload in isolated
iframes and proves their console output matches.
python3 server.py # http://localhost:32552
server.py is zero-dependency (stdlib only): it serves the static UI and the REST
API. The port defaults to 32552 and is configured in config.ini
([server] port = ...) or overridden by OBFUSCATE_PORT.
| Method | Route | Description |
|---|---|---|
| GET | /api/health |
{"status": "ok"} |
| GET | /api/config |
host, port, stage list, corpus size |
| GET | /api/corpus?n=8 |
random dead-code snippets + corpus size |
| POST | /api/obfuscate |
run the pipeline server-side |
POST /api/obfuscate body (all fields except code optional):
{"code": "console.log('hi');", "seed": 42, "stages": ["base64", "chunks"],
"declarations": 1500, "caves": 420, "stringRatio": 0.8, "commentProbability": 0.15}Example:
curl -X POST http://localhost:32552/api/obfuscate \
-H 'Content-Type: application/json' \
-d '{"code": "console.log(\"hi\");", "seed": 42}'
Counts are clamped (declarations ≤ 20000, caves ≤ 5000) and payloads over
512 KB are rejected. A fixed seed makes the response fully deterministic.
docker build -t obfuscatejs:latest .
docker run --rm -p 32552:32552 obfuscatejs:latest # web UI + API on :32552
The image is based on python:3.12-slim, installs nothing (stdlib only), and runs
as an unprivileged user with a /api/health healthcheck. CLI stages work too:
docker run --rm -v "$PWD:/work" obfuscatejs:latest \
python3 obfuscate.py --file /work/payload.js -o /work/obfuscated.js --seed 42
docker-compose.yml is included as a convenience (docker compose up --build).
| Script | What it does |
|---|---|
bash start.sh |
Builds the image if missing and (re)starts the obfuscatejs container on the configured port (--build forces a rebuild) |
bash test.sh |
Builds the test image target (adds pytest + Node) and runs the entire suite inside Docker; extra args are forwarded to pytest |
sudo bash install.sh |
Installs to /opt/obfuscatejs, registers and enables a systemd service, and starts it — the container survives reboots via Restart=always + --restart unless-stopped |
Uninstall: sudo systemctl disable --now obfuscatejs && sudo rm /etc/systemd/system/obfuscatejs.service && sudo rm -rf /opt/obfuscatejs
| Tag | When to use |
|---|---|
obfuscatejs:latest |
Mutable pointer to the current stable release |
obfuscatejs:2.1.0 |
Immutable semantic-version release (REST API + scripts) |
obfuscatejs:2.1, obfuscatejs:2 |
Floating minor/major tags that track compatible releases |
obfuscatejs:test |
Test image built from the test target (pytest + Node inside) |
obfuscatejs:git-<short-sha> |
CI builds, e.g. obfuscatejs:git-3c8e5f1 |
ghcr.io/<owner>/obfuscatejs:<tag> |
Same tags prefixed when pushing to a registry |
Build once, tag many:
docker build -t obfuscatejs:2.1.0 -t obfuscatejs:2.1 -t obfuscatejs:2 -t obfuscatejs:latest .
The project ships with a pytest suite covering every stage, the JS pipeline port, the REST API, config loading, and the web server — including Node.js execution-equivalence checks that prove the obfuscated output behaves identically to the original payload:
pip install pytest
python3 -m pytest tests/
Or run the entire suite inside Docker (test image includes pytest + Node):
bash test.sh
Tests that execute JavaScript require a node binary; they are skipped automatically
when Node is not installed (the Docker test target installs Node, so nothing skips there).
JsToBase64is a prerequisite forAtobToChunksAtobToChunksis a prerequisite forAtobChunkVariableSplitterto workAddDeadCodemines JS files from./deadcode(the more the better); if the corpus is missing or empty it falls back to built-in snippets instead of crashing
- New: REST API —
POST /api/obfuscate(validated, seeded, clamped),GET /api/config; web UI gained a browser/server engine toggle - New:
config.ini— server config, default port 32552, env-var overrides - New:
test.sh(full suite inside Docker via thetestimage target),start.sh(build + run on configured port),install.sh(/opt/obfuscatejs+ systemd, survives reboots) - New:
webui/— terminal-style web interface; full pipeline ported to browser JS (pipeline.js), sandboxed equivalence verification, live entropy stats - New:
server.py— zero-dependency stdlib server for the web UI with/api/healthand/api/corpus - New: Docker support —
Dockerfile(slim, unprivileged, healthcheck),.dockerignore,docker-compose.yml - New:
StringEscaper.pystage — hex/unicode-escapes string literals (both quote styles) - New:
obfuscate.py— run the entire pipeline in one command, with--seed,--skip, and per-stage stats - New:
entropy.py— cross-platform Python port ofentropy.sh, with--compare - New: full pytest unit-test suite in
tests/(109 tests, incl. Node execution-equivalence) - Fix:
AddDeadCodeno longer crashes when./deadcodeis empty or missing, and itstypeof window === 'undefined'guard is now always-false in Node as well as browsers - Fix:
AddRandomDeclarations/AddDeadCodenow honor--count/--cavesexactly (previously capped by the input's line count), and injected names are guaranteed unique - Fix:
AtobChunkVariableSplitterno longer useseval()on input and locates the chunk array by pattern instead of a hardcoded line number - Fix:
make_html.pyis now a proper CLI (--template,--replacement,--output) instead of crashing on import
Mad respect to paopao2 for his repo of JS leetcode, I have added snippets of homie's code.