# paker: load encrypted Python packages from memory

> Encrypt a Python package tree, ship it over the network, load it from memory. What works, what doesn't.

2026-05-06 · Wojciech Wentland · https://blog.wentland.io/blog/paker-encrypted-in-memory-python-loader/

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](https://github.com/desty2k/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.

```python
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, the Anthropic SDK, and a dozen more packages all work, including ones with compiled C code.

## How it loads

paker loads from memory with nothing on disk on Linux, and on Windows for extensions without static TLS (those fall back to a temp file). 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. The platform details are in [a separate post](https://blog.wentland.io/blog/loading-code-without-the-disk/).

## 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`:

```python
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 built an obfuscator before I understood my own threat model well enough to realize I didn't need one. Separate post coming on that.

## Install

```bash
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](https://github.com/desty2k/paker).
