Skip to content

Security

lodum is designed with a secure-by-default philosophy. This document describes the security model, built-in protections, and guidelines for safe usage.

Threat Model

lodum deserializes data that may come from untrusted sources. The primary attack surface is during deserialization (the load() / loads() functions across all format modules), where maliciously crafted input could attempt:

  • Arbitrary code execution
  • Denial of service (stack exhaustion, memory exhaustion)
  • Unauthorized access to system resources

Serialization (dump() / dumps()) is considered low-risk, as it only reads Python objects that the application code has already constructed.

SafeUnpickler (Pickle Module)

Pickle is inherently unsafe for untrusted data — a crafted pickle can execute arbitrary code during deserialization. lodum.pickle mitigates this risk with a SafeUnpickler that enforces a multi-layer allowlist approach.

How SafeUnpickler Works

SafeUnpickler lives in src/lodum/pickle.py and applies three layers of defense:

1. System Module Blocklist

if "os" in module_name or "sys" in module_name or "subprocess" in module_name:
    raise pickle.UnpicklingError(f"Unsafe module '{module_name}' is forbidden.")

Any attempt to unpickle objects from dangerous modules (os, sys, subprocess, and any module containing these names) is rejected immediately.

2. Builtins Allowlist

When a class comes from builtins, it is only permitted if it is one of the following safe types:

Allowed Types
int, float, str, bool, bytes, bytearray
list, tuple, dict, set, frozenset
complex, NoneType, type

Any other builtins name (including eval, compile, __import__) is rejected.

3. Known-Safe Library Types

A small set of standard library types from collections and array are explicitly permitted:

  • collections.defaultdict, OrderedDict, Counter
  • array.array, _array_reconstructor

4. Lodum-Enabled Class Allowlist

For classes from arbitrary third-party modules, SafeUnpickler checks for the _lodum_enabled attribute — a marker set by the @lodum decorator at import time. Only classes explicitly decorated with @lodum can be unpickled from external modules.

This means:

  • If you decorate a class with @lodum, it can be safely pickled and unpickled.
  • If someone sends you a pickle containing an arbitrary os.system call or a random application class, SafeUnpickler will reject it.

Usage

import lodum.pickle as pickle

# Safe: deserializes only @lodum-enabled objects
obj = pickle.load(MyLodumClass, pickle_bytes)

Input Size Limits

All format modules enforce a maximum input size to prevent memory exhaustion from oversized payloads:

  • DEFAULT_MAX_SIZE: 10 MB (10 * 1024 * 1024 bytes)
  • Applied to string and bytes inputs in json, yaml, toml, msgpack, cbor, bson, and pickle modules.
from lodum.core import DEFAULT_MAX_SIZE  # 10485760

# Custom limit
obj = json.load(MyClass, data, max_size=5 * 1024 * 1024)  # 5 MB

Recursion Depth Limits

To prevent stack exhaustion from deeply nested data:

  • DEFAULT_MAX_DEPTH: 100 levels
  • Enforced during both serialization and deserialization at every level of object traversal.
from lodum.core import DEFAULT_MAX_DEPTH  # 100

The limit is checked in the compiled dump/load handlers via the generated bytecode, providing efficient O(1) per-level cost.

exec() Code Generation

lodum uses Python's compile() (not exec()) to compile generated AST nodes into bytecode functions for each @lodum-decorated class. While this is safer than string-based exec(), there are still considerations:

Why It Is Safe

  1. Trusted source: The AST is generated from type hints defined in your application code at import time, not from external data.
  2. Compile, not exec: The generated code is compiled into a code object and stored in the context cache. It is only executed when called as a function, not at generation time.
  3. Stack depth protection: Every compiled handler includes a recursion depth check before processing nested structures.

Recommendations

  • Do not decorate classes that are constructed from untrusted input or user data.
  • Do audit your @lodum-decorated classes as part of your code review process.
  • Do deploy in environments with restricted execution policies that permit compile() (this is standard and built into Python).

JSON Schema Generation

The lodum.schema() function generates JSON Schema (Draft 2020-12) descriptions for @lodum classes. Schema generation is read-only — it inspects type hints and field metadata, does not execute arbitrary code or deserialization logic. It is safe to use on any data.

Reporting Security Issues

If you discover a security vulnerability in lodum, please report it responsibly:

  1. Do not open a public GitHub issue.
  2. Email zopemaven@gmail.com with details.
  3. Allow time for a fix before public disclosure.

We aim to acknowledge reports within 48 hours and release patches within 7 days for critical issues.

Security Changelog

Date Version Description
2026-02-11 0.2.0 Initial SafeUnpickler with multi-layer allowlist in lodum.pickle.
2026-02-21 0.3.0 Input size limits (DEFAULT_MAX_SIZE) and recursion depth limits (DEFAULT_MAX_DEPTH) added to all format modules.