Encrypting Code
protect and run, expiry, and the five protection levels — from freely-distributable keyless artifacts to a machine-locked two-factor vault.
magiclock protect turns a .py file into a .pya — an encrypted, gated container — without a compiler and without uploading your source anywhere. magiclock run runs it.
Basic usage
# First run: signs you in and activates this machine, then encrypts.
# Every encrypt after that is instant and non-interactive.
magiclock protect app.py # -> app.pya
magiclock run app.pya
# Point at a directory to protect a whole project tree recursively
# (.venv/venv/__pycache__/.git/dist/build are skipped automatically).
magiclock protect src/.pya files are ordinary files you can commit, copy, or ship however you already distribute your app — the encryption travels with the file. By default the artifact is portable and keyless: it decrypts transparently on any machine that has magiclock installed, with no account, passphrase, or machine lock on the running side. That makes the default a convenience/obfuscation level — your source doesn't ship in the clear, but anyone with the file and the runtime can execute it. When you need real access control, pick a stronger level below.
Choosing a protection level
| Level | Flags | Who can decrypt |
|---|---|---|
| Keyless portable (default) | (none) | Anyone with magiclock installed — convenience/obfuscation grade. |
| Passphrase portable | --passphrase | Anyone who knows the passphrase, on any machine. |
| Key portable | --emit-key | Anyone holding the generated key, on any machine. |
| Machine-locked | --bind-machine | Only this activated machine — a non-exportable machine key plus the license gate; the strongest single-factor level. |
| Machine + passphrase | --bind-machine --passphrase | This activated machine and the passphrase — two-factor. |
How it works
A .pya is a small versioned container wrapping an encrypted envelope. magiclock run app.pya decrypts it in memory, installs an import hook for anything it imports, and executes it — nothing is ever written back to disk as plaintext. Because the marshaled bytecode inside is tied to the CPython version that encrypted it, a .pya built on Python 3.12 won't load on 3.13; re-run protect after upgrading your interpreter.
Removing the plaintext
magiclock protect app.py --remove # encrypt, then delete app.pyExpiry: time-limited and self-destructing artifacts
These flags stamp an expiry into the encrypted artifact itself — independent of your license — after which it refuses to decrypt:
magiclock protect app.py --expires-in 30d # stops decrypting in 30 days
magiclock protect app.py --expires-at 2026-12-31 # stops decrypting on a date
magiclock protect app.py --trial # fixed 48-hour self-destruct — for demos and evaluations--trial, --expires-in, and --expires-at are mutually exclusive — pick one per file.
Portable with a secret: passphrase or key
The keyless default asks nothing of whoever runs the artifact. To keep the portability but add an access secret, pick one of two portable modes:
# Passphrase-protected, still runs on any machine:
magiclock protect app.py --passphrase
magiclock run app.pya --passphrase
# Or hand out a generated key instead of a passphrase:
magiclock protect app.py --emit-key
magiclock run app.pya --key <the-printed-key>--passphrase and --emit-key are mutually exclusive — pick one secret per artifact.
Strongest: machine lock (--bind-machine)
When the artifact must not leave your machines at all, bind it to this activated machine — decryption then requires this machine's non-exportable vault key and passes the license gate, and the file is useless anywhere else:
magiclock protect app.py --bind-machine
magiclock run app.pya # only works on the machine that encrypted itAdd a passphrase on top for a two-factor lock — the right machine and a secret:
magiclock protect app.py --bind-machine --passphrase
# prompts for a passphrase (or reads $MAGICLOCK_PASSPHRASE non-interactively)
magiclock run app.pya --passphrase--emit-key is portable-only and mutually exclusive with --bind-machine — a machine-locked artifact's key never leaves the machine, so there is nothing to hand out.
For CI and scripted runs, both the passphrase and a portable key can come from the environment instead of a prompt:
export MAGICLOCK_PASSPHRASE=... # for --passphrase artifacts (portable or two-factor)
export MAGICLOCK_DECRYPT_KEY=... # alternative to `run --key` for --emit-key artifacts
magiclock run app.pyaWhat's next
- Encrypting Models & Resources applies the same expiry and protection-level options to non-code assets.
- Account & Activation covers what happens when you're offline, or when a device is revoked.
- CLI Reference lists every flag for
protectandrunin one place.