ssukka
쓰까(ssukka) is Busan dialect (부산 사투리) for "mix it up" (섞어).
HTML obfuscation library and CLI for Rust. Renders identically in browsers but is hard for humans to read.
Features
Cosmetic (on by default)
- Class/ID renaming - consistent across HTML, CSS, and JavaScript (including dynamic construction patterns)
- HTML entity encoding - text and attribute values encoded as decimal/hex/named entities
- Tag case randomization -
<div>becomes<DiV> - Attribute shuffling - randomized attribute order
- CSS minification - via lightningcss
- CSS selector unicode escaping -
.foobecomes.\66\6f\6f - JS string encoding - string literals encoded with a randomized mix of
\xHH/\uXXXX/\u{..}(strict-mode-safe) - JS minification - comment removal and whitespace compression
- Comment removal and whitespace collapsing
- Deterministic output - seed-based RNG for reproducible results
Advanced (opt-in)
These change the DOM, output size, runtime cost, or accessibility, so they are off by default:
- Honeypots / decoys (
--honeypots N) - inject invisible trap links, fake form fields, and bogus data blocks to waste scraper effort and poison harvested data. Hidden from layout and assistive tech. - Structural obfuscation (
--structural) - move visible text out of the static markup into base64data-attributes, restored client-side by an injected script. Resists static scrapers (curl / readability extractors that don't run JS) while rendering identically. Warning: breaks no-JS, SEO, and degrades accessibility. - AST JS engine (
--js-ast, powered by oxc):- Identifier mangling (
--mangle) - scope-aware renaming of local JS bindings (never globals, so cross-script / inline-handler references stay intact). - String array (
--js-string-encoding array) - hoist string literals into a base64 array decoded at runtime. - Dead code injection (
--dead-code) - opaque-predicate-guarded junk that never executes. - Control-flow flattening (
--cff) - reshape sequential logic into a shuffledswitchdispatcher.
- Identifier mangling (
- Polymorphic mode (
--polymorphic) - vary which transforms run (and how) on every invocation, so identical input yields structurally different output each time (signature/cache evasion). - WASM build - runs in the browser / Cloudflare Workers / Deno via the
wasmfeature.
Every AST transform re-parses its own output and is discarded if it would emit invalid JavaScript; on a parse failure the engine falls back to the token-based path. The transforms are semantics-preserving by construction (verified by executing obfuscated output under Node).
Threat model
ssukka raises the cost of reading and scraping a page; it is not a security boundary. Anything the browser can render, a determined adversary with a headless browser can recover. Use it to deter casual copying and cheap bulk scraping, not to protect secrets.
Modern LLM-based deobfuscators can reverse simple identifier renaming and string encoding, so the strongest configurations layer transforms (string array + mangling + structural + honeypots) and lean on structural/visual approaches rather than renaming alone. Benchmark the presets with cargo bench.
Offline by design
ssukka performs no network I/O at runtime - verified with strace (zero socket/connect syscalls). All dependencies (lol_html, lightningcss, oxc, rand) are pure Rust and compile from source. External resource inlining (--inline-local-resources) reads local files only and never fetches over the network.
Architecture
Streaming pipeline built on lol_html:
Input HTML
-> Pass 0 (optional): inline local CSS/JS
-> Pass 1: analyze -> SymbolMap
-> Pass 2: transform HTML / CSS / JS
-> Output HTML
Installation
Or build from source:
CLI Usage
# Basic
# stdin/stdout
|
# With options
Options
| Flag | Description |
|---|---|
-i, --input <FILE> |
Input HTML file (default: stdin) |
-o, --output <FILE> |
Output file (default: stdout) |
--seed <N> |
Seed for deterministic output |
--no-rename |
Disable class/ID renaming |
--no-minify-css |
Disable CSS minification |
--no-minify-js |
Disable JS minification |
--no-encode-entities |
Disable entity encoding |
--no-shuffle-attrs |
Disable attribute shuffling |
--no-randomize-case |
Disable tag case randomization |
--js-string-encoding <none|escapes|array> |
JS string strategy (default: escapes) |
--honeypots <N> |
Inject N invisible decoy nodes (scraper traps) |
--structural |
Move text into encoded attrs, restore client-side |
--polymorphic |
Randomize transforms per run (ignored with --seed) |
--js-ast |
Use the oxc AST engine for <script> JS |
--mangle |
Scope-aware local identifier renaming (implies --js-ast) |
--cff |
Control-flow flattening (implies --js-ast) |
--dead-code |
Opaque-predicate dead code injection (implies --js-ast) |
--dead-code-threshold <0..1> |
Fraction of sites that receive dead code |
--inline-local-resources |
Inline local <link>/<script src> (offline only) |
--base-dir <DIR> |
Base directory for resolving local resources |
# Maximum: layered obfuscation for the strongest output
Library Usage
// Simple
let result = obfuscate?;
// With configuration
let result = builder
.seed
.rename_classes
.rename_ids
.encode_text_entities
.minify_css
.build
.obfuscate?;
// Advanced, layered
use JsStringEncoding;
let result = builder
.inject_honeypots
.honeypot_count
.structural_obfuscation
.js_ast
.mangle_identifiers
.js_string_encoding
.dead_code_injection
.control_flow_flattening
.build
.obfuscate?;
WASM
# or: wasm-pack build --features wasm
Exposes obfuscate(html), obfuscate_seeded(html, seed), and obfuscate_max(html, honeypots, seed).
Limitations
- The default JS path (no
--js-ast) is token-based: string encoding + minification only. Enable--js-astfor AST-grade mangling / string arrays / dead code / control-flow flattening. - Dynamic class/ID construction in JS is handled via prefix-detection heuristics on the token path; highly dynamic patterns may not be caught.
- AST control-flow flattening is conservative: it only flattens top-level sequences of simple expression statements (anything with declarations or control flow is left as-is to guarantee correctness).
- External stylesheets/scripts are only processed with
--inline-local-resourcesand only from the local filesystem. - Obfuscation is a deterrent, not security - see Threat model.
Development
Requires Rust >= 1.94 (pinned in rust-toolchain.toml). A Dockerfile and a Nix flake (nix develop / nix build) are also provided. The repo ships POSIX sh tooling:
License
MIT