Obfuscation levels
The --level 0–5 flag is Obfy's sophistication dial. Each level adds to the
one below it. Whatever the level, the output is always compiled → marshalled →
AES-256-GCM encrypted by the loader.
--level | Adds (includes everything below it) |
|---|---|
0 | Nothing — code is compiled, marshalled, and encrypted as-is. |
1 (default) | Strip docstrings. (Comments are dropped inherently by recompiling from the AST.) |
2 | + mangle string literals (XOR decoder) + inject dead code. |
3 | + rename function-local variables and in-function nested defs/classes. |
4 | + cross-module public-name renaming across the whole tree. |
5 | + native function compilation: eligible functions are lowered to the Obfy clean-room VM so their CPython bytecode never ships. Unsupported functions fall back to level 4. |
Choosing a level
- Levels 1–2 are safe for virtually any project and add no semantic risk.
- Level 3 renames only function-local names, so it's low-risk too.
- Level 4 renames public names across modules. This is aggressive: code that
looks names up dynamically (e.g. by string, via
getattr, or framework auto-discovery) may break. Use--excludefor files that rely on dynamic lookup. - Level 5 additionally compiles eligible functions to a native VM. Coverage is partial and growing; anything unsupported transparently falls back to level 4, so the build still succeeds.
obfy build --src ./app --out ./dist --level 4
Level 5 and the JIT
Level 5 runs on a default interpreter for the compiled functions. An opt-in
JIT (set the OBFY_NATIVE_JIT environment variable) emits control-flow-flattened,
per-build polymorphic machine code for stronger protection. It is opt-in because
native execution has stricter runtime requirements; leave it off unless you've
validated your build with it.
Honest posture
Higher levels raise the cost of recovering your code but do not make it impossible — CPython must eventually run real bytecode. Treat Obfy as deterrence, and pair it with legal protection for sensitive IP.