PyInstaller bundles Python code into a standalone binary. Run pyinstxtractor on that binary and you get every .pyc back in about ten seconds. uncompyle6 turns those into readable source. The “compiled” binary is a zip file with extra steps.
paker takes a different approach. The binary ships without the proprietary code. At runtime, encrypted modules arrive from the network and load directly into memory.
import paker, requests
# Key arrives from your license server. paker doesn't manage keys.
key = requests.post("https://license.example.com/key",
json={"license": LICENSE_ID}).content
# loads() accepts dict, str, bytes, or bytearray.
bundle = requests.get("https://cdn.example.com/sdk.paker").json()
with paker.loads(bundle, key=key):
import proprietary_sdk
proprietary_sdk.run()
No pip install on the client. Python modules get compiled from memory. Native extensions load through platform-specific loaders. numpy, pydantic, boto3, Pillow, anthropic, and a dozen more packages all work, including ones with compiled C code.
How it loads
paker implements zero-disk loading natively on Windows and Linux. macOS requires an ephemeral temp file (written, loaded, immediately deleted) because of mandatory code signing, and the host binary needs the disable-library-validation entitlement. I’ll go into the platform-specific details in a separate post.
What it doesn’t protect against
Once loaded, modules live in sys.modules as normal Python objects. Someone with access to the running process can inspect bytecode through marshal.dumps(func.__code__) or walk the heap with gc.get_objects(). Compiled code objects stay resident in the process. mlock and MADV_DONTDUMP keep them out of core dumps, not out of the address space.
A determined attacker with the key and a debugger gets through. This is defense in depth against casual reverse engineering. If someone with a valid license wants to spend a week extracting bytecode, the real protection is legal, not technical.
What paker does protect: the bundle at rest. Without the key, the encrypted payloads are AES-256-CTR + HMAC-SHA256 ciphertext. The key and the bundle always travel separately, through whatever key management you already have (license server, HSM, environment variable). paker handles encryption and loading. Key distribution is your problem.
Transform hooks
paker ships hooks instead of a built-in obfuscator. Pass any callable as ast_transform or code_transform:
import ast, paker
def strip_docstrings(tree: ast.AST, module_name: str) -> ast.AST:
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef,
ast.ClassDef, ast.Module)):
if ast.get_docstring(node):
node.body.pop(0)
return tree
bundle = paker.dumps("myapp", key=KEY, ast_transform=strip_docstrings)
There’s also a code_transform hook for bytecode-level passes. I spent days building a built-in obfuscator before I understood my own threat model well enough to realize I didn’t need one. Separate post coming on that.
Install
pip install paker
Source, docs, examples (including a remote agent that ships the anthropic SDK over TCP to a zero-install client): github.com/desty2k/paker.