Docs / Security model
Nyami defends a build at every level instead of leaning on a single technique. Source, bytecode, encryption and runtime all carry their own protection, and each layer assumes the others can eventually be reached. No client-side protection is unbreakable, and this page describes what each layer raises the cost of, not what it makes impossible.
Lowering is the first structural change. The call sites of Python builtins are replaced with native implementations in the kernel across 14 families including I/O, so the operations your code performs are no longer recognisable Python calls. The kernel exposes native replacements for 141 lowered operations, and nested lowered calls are fused into a single kernel dispatch.
The call sites of Python builtins are replaced with native implementations in the kernel across fourteen families, I/O among them. Each family can be controlled independently.
The kernel exposes native replacements for 141 lowered operations. After the pass, the work your code performs no longer exists as a recognisable Python call in the recovered source.
Nested lowered calls are fused into a single kernel dispatch. A chain of operations that would otherwise be a readable sequence of calls collapses into one entry point.
A builtin call site hands a reader the operation for free: the name alone says what is happening. Once that site is a kernel dispatch, the reader has to reverse the kernel to learn anything, and the kernel is the part that is renamed and re-issued on every build.
The obfuscation pass works on what is left after lowering. It changes the constants, the function bodies, the control flow and the numeric vocabulary of the program, and it adds material that exists only to be analysed.
String constants are encrypted with ChaCha20 and decrypted only where they are used, so plaintext strings no longer sit in the artifact waiting to be read.
Individual function bodies are extracted and encrypted, then decrypted at call time. Nothing readable is left in place between calls.
Function bodies are compressed and serialised into compact byte arrays. It is lighter than the full pass and applied by default.
Control flow is rewritten into a switch-based state dispatch, so the original branch structure is no longer visible in the emitted code.
Branches whose outcome is fixed at build time are inserted so the flow a reader sees does not match the flow that actually runs.
Dead code is injected into the output, adding paths that exist only to be analysed and discarded.
Comments and docstrings are stripped and the remaining structure is compacted, which removes the documentation a reader would otherwise start from.
Numeric literals are rewritten as hex, octal or binary. This is on by default; leave it off for scripts that embed images or binary data.
Mixed Boolean-Arithmetic rewriting replaces arithmetic expressions with equivalent forms that are harder to reduce back to the original expression.
Automated decompilers reconstruct source from bytecode by relying on structures that are stable in normal Python output. Nyami corrupts those structures, so the reconstruction those tools produce does not match what the artifact executes.
The bytecode structures that automated decompilers depend on are corrupted, so the reconstruction those tools attempt does not match what the artifact actually executes.
Code objects carry misleading filenames, so any structure that does survive recovery points at the wrong origin.
The corruption is aimed at pylingual, pycdc and uncompyle6 specifically, rather than being a generic hardening pass.
Everything above happens before sealing. The sealing pass is what turns the hardened payload into the artifact you ship, and it is where a fresh set of key material is generated for every build.
The bytecode is encrypted with AES-256-GCM using a fresh key and a fresh nonce for every build. Key material is never reused between builds of the same file.
The payload is split into individually sealed sections that are streamed back one at a time, so the whole artifact is never resident as a single decrypted block.
A Feistel white-box layer wraps the sealed payload with 8 to 12 rounds, and the round count is randomised per build.
Code objects are emitted through a custom serializer that the standard marshal and pickle modules cannot parse, so the usual tooling has nothing to load.
The sealed payload then passes through a chained multi-encoding pipeline before it is written out.
The key and nonce are generated per build, not per account or per file. Two builds of the same source do not share key material, and a key recovered from one artifact has no value against another.
Every build differs from the last. Fresh keys and nonces are issued, kernel operations are renamed, the cipher round count is randomised, and decoys are regenerated. The result is that two builds of the same file share no stable names and no shared key material.
This is what removes the value of comparing builds. A description of one artifact does not describe the next one, and a signature written against a name in one build has nothing to match in the build after it.
Sealing protects the artifact at rest. The runtime layers protect it while it runs, on the machine that is executing it. They are selected by the security tier: the Python-level tier, the machine and virtual-machine tier, or both together.
| Layer | What it does |
|---|---|
| Anti-debugger | Eleven detection families covering debugger processes and modules, virtual machines, timing drift, hardware breakpoints, stack manipulation, trace functions and debug environment variables. Detection terminates the process. |
| Anti-VM detection | Twelve confidence-weighted methods covering CPU vendor strings, MAC address prefixes, BIOS and firmware data, hardware device enumeration and hypervisor flags. |
| Kernel driver detection | Detects bring-your-own-vulnerable-driver attacks against known malicious kernel drivers, at load time and continuously. It is off by default, and must stay off for scripts that legitimately load drivers. |
| Anti-monkey-patch | HMAC-SHA256 challenge-response verification over critical builtins and import hooks. It guards against module hijacking and blocks reloading protected modules. |
| Integrity checks | Layered verification over a secure serializer with no fallback to standard marshal. Frozen module snapshots and function bytecode hashes are re-verified at runtime, so a modified code object fails closed. |
| Anti-dump | Decrypted code objects, bytecode buffers and constant tables are overwritten after use, and key material is zeroed. |
| Memory page protection | Code and key memory is locked and re-protected after loading, so it is no longer writable. |
| Guard-page traps | Sealed sections can be placed on guard pages that trap access outside the expected window. |
| Hydra anti-tamper | Layered monitors run in parallel: a high-frequency monitor, a secondary background monitor, and the native kernel watchdog. Any layer that observes tampering wipes key material and terminates. |
| Anti-trace and anti-profile | Tracing and profiling entry points are captured and monitored. |
| Audit hook protection | Audit hooks are installed at bootstrap and guarded against replacement, blocking unauthorised syscalls, subprocess creation and debugger attachment. Planting from site-packages is detected. |
| Caller frame validation | Every execution path verifies that it was entered from the legitimate protected context. |
| Runtime API name gate | Privileged call names resolve through a per-process mapping instead of appearing as literals in the artifact. |
| Native kernel hardening | The kernel verifies its own imports and the host interpreter's code and export tables at runtime. |
| On-tamper self-corruption | A detached child process overwrites the artifact with same-length garbage after the parent dies, so a captured artifact is unusable once the process it came from is gone. |
| Secure cleanup | Intermediate build artifacts, generated sources and temporary directories are overwritten with random data before deletion. |
The layers are deliberately layered in depth rather than stacked in a single check. A monitor that is disabled by the operator does not remove the others, and the watchdog runs in native code so it does not depend on the interpreter it is watching.
There is one failure path, and every tamper trigger routes into it. It wipes key material and exits on the first strike, so a partial detection is still a terminal one. There is no degraded mode, no retry and no path that continues running with compromised state.
On-tamper self-corruption is the part that reaches past the running process. A detached child process overwrites the artifact with same-length garbage after the parent dies, which means a dump taken from a live process does not leave a usable artifact behind. Secure cleanup applies the same idea to the build side: intermediate artifacts, generated sources and temporary directories are overwritten with random data before deletion.
Nothing that runs client side is unbreakable. These layers raise the cost and the time required to reach the code, and they make automated recovery unreliable, but a determined analyst with the artifact in hand and enough time can still get somewhere. Treat the artifact as one part of your protection, not the whole of it. Do not ship credentials, API secrets or licence keys in plaintext inside a protected script, and do not treat obfuscation as a substitute for keeping a secret on a server you control.