Security Model
What the runtime gate actually checks, why device-lock errors look the way they do on purpose, and the honest limits of each tier.
Protection isn't a single flag that's either present or not — it's a runtime check, inserted into every gated module, that runs every time that module is imported, entirely offline, using cryptographic material that never leaves the device it belongs to.
What the gate checks
Every time gated code runs, MagicLock verifies, locally and in this order:
- The license is genuine — it was issued by your account and its signature verifies.
- The license still counts — not expired, not revoked.
- The device matches — this is the exact machine the license (or artifact) is bound to.
- The capability is allowed — what's about to run is within what the license grants.
- The proof is fresh — a one-time credential rules out a captured-and-replayed old check.
If any step fails, execution stops before your protected code runs — there's no partial or "best effort" path.
Why a wrong machine and a tampered file look the same
VaultLockedError (see API Reference) is raised both when an artifact is opened on the wrong machine and when its local state has been altered. This is deliberate: distinguishing the two cases would mean leaking information about why verification failed, which is exactly the kind of oracle an attacker probing for weaknesses would want. The error message stays the same either way.
Tamper-evidence, not just tamper-detection
Encrypted artifacts are authenticated, not just encrypted — a single flipped byte fails authentication before decryption even starts. There's no partial decrypt of a modified file to inspect; it simply refuses to run.
What each tier actually resists
Be precise about the trade-off you're making:
- Convenience tier (
.pya) — stops casual copying and casual source-code viewing. While your app runs, the decrypted bytecode necessarily exists in the process's memory; a sufficiently motivated attacker with a debugger and full control of the running process can, in principle, extract it. This tier is aimed at "don't make it trivial," not at defeating memory forensics. - Compiled tier (
magiclock build) — no plaintext Python ships at all, and the license check is auto-inserted into every compiled module's control flow at build time, rather than living behind one shared, deletable check. This closes off the simplest bypass ("just delete the check") and raises the bar from "read the source" to "reverse-engineer a compiled binary, module by module." - Device binding — ties decryption to a machine fingerprint derived from local hardware/OS identifiers. It resists casual copying between machines; it isn't a substitute for a hardware secure element, and a sufficiently resourced attacker who fully controls a device can attempt to spoof its fingerprint.
MagicLock is designed to make copying, tampering, and reverse-engineering meaningfully more expensive — not to claim that any software-only protection is unbreakable against an attacker with unlimited time and full control of the hardware. If your threat model includes that level of adversary, that's what hardware-backed protection is for.
Signing and keys
Licenses are signed with Ed25519 and verified against your vendor key — the same signature scheme secures revocation lists, so a stale or forged revocation can't be replayed to un-revoke something. Verification keys are embedded in what you ship; the private signing key never leaves the license server.
See also
- Account & Activation — the offline grace window and what revocation does and doesn't affect.
- Python API Reference — the exceptions each failure mode raises in your code.