# Fickling: a pickle decompiler and static analyzer from Trail of Bits

> Fickling reads Python pickle data without executing it, decompiles it to an AST, and can hook the pickle module so unsafe models raise UnsafeFileError. It is a security tool for ML and Python teams, not a general serializer.

**trailofbits/fickling** — A Python pickling decompiler and static analyzer

- Repository: https://github.com/trailofbits/fickling
- Stars: 670 · Forks: 78
- Language: Python
- License: LGPL-3.0
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/trailofbits-fickling

## What problem Fickling solves, and for whom

Python's pickle format is a small stack-based virtual machine. A pickle stream is a list of opcodes, and one of those opcodes, GLOBAL, names a callable that the unpickler will import and eventually call. That is the whole attack surface: loading a pickle can import and execute code, so a .pkl file is closer to a program than to a data file. Fickling exists to read that program without running it. The README describes it as a decompiler, static analyzer and bytecode rewriter for pickle object serializations, and it explicitly covers pickle-based files including PyTorch files.

The intended audience is narrow and specific. Security engineers triaging a suspicious model artifact, ML platform teams who accept model files from users or third parties, and incident responders who need to know what a pickle would have done. It is not aimed at application developers who just want to persist a dictionary to disk. If the pickle never leaves your process, Fickling has nothing to add. The moment a pickle crosses a trust boundary, it does.

## How Fickling analyzes a pickle without executing it

The core object is fickling.fickle.Pickled. Calling Pickled.load on a pickle byte stream produces an object whose .ast attribute is a Python ast.Module. The README shows the round trip: pickle.dumps([1, 2, 3, 4]) goes in, and ast.dump prints an Assign node binding result to a List of four Constant nodes. The pickle opcodes are lifted into Python source structure, which means the analysis runs over an AST rather than over raw bytes, and the output can be read by anyone who knows Python's ast module.

On top of that representation sit the safety checks. The README's example output for an unsafe file shows a severity of OVERTLY_MALICIOUS, an analysis string explaining that eval(b'[5, 6, 7, 8]') was assigned to _var0 and never used, and a detailed_results block naming OvertlyBadEval and UnusedVariables. That unused-variable signal is the interesting part: a call whose result is discarded is a strong hint that the call existed for its side effect, not its return value. The exception carries this structure as e.info, so a caller can branch on severity rather than parsing prose.

Separately, the CLI can trace the pickle virtual machine. The README states that fickling --trace file.pkl traces execution without exercising malicious code. That is a different mode from static analysis: it walks the opcode stream and reports what the machine would do, which is useful when you want the sequence of imports and calls rather than a verdict.

## Installing Fickling and checking your first file

Fickling is published on PyPI and the README gives both pip and uv installs. It has very few dependencies by design; the only required one in pyproject.toml is typing-extensions, and only for Python versions below 3.12. PyTorch is optional.

```bash
python -m pip install fickling
```

After that, the fastest way to get a verdict on a file is the CLI safety check. The README uses this exact invocation for non-Python callers:

```bash
fickling --check-safety -p pickled.data
```

Run it against a pickle you do not trust. You get a safety verdict rather than a loaded object, which is the point: nothing in the file is executed to produce the answer. If you want to see what the file would do, switch to the trace mode.

```bash
fickling --trace file.pkl
```

The README says this traces the pickle virtual machine safely. Expect a walk through the opcodes and the imports they reference, not a Python source listing. For a source listing, use the library API and print the AST. The README's decompilation example loads a pickle of a list and dumps the AST as a Module containing a single Assign. To use Fickling as a runtime guard instead, install the hook before any library that unpickles is imported.

```python
import fickling

fickling.hook.activate_safe_ml_environment()
```

The README is explicit that this must run before importing torch, numpy, or any other library that uses pickle, because the hook patches the pickle module globally. Once active, imports made while loading a model are checked against an allowlist drawn from ML libraries, and anything outside it is blocked. If a legitimate model trips the allowlist, the hook takes an also_allow list of import names, and the README warns that you must be confident those imports cannot be turned into arbitrary code execution.

## The allowlist is the product, and it is also the weak point

activate_safe_ml_environment is the feature most teams will actually deploy, and it is worth being blunt about what it does. It does not prove a model is safe. It checks the imports a pickle performs against a fixed allowlist of imports from ML libraries that the maintainers consider safe, and blocks files containing other imports. A file that only imports allowed names passes, whether or not its weights or its remaining opcodes are benign. The README's own guidance for also_allow makes the same admission from the other direction: you should only add imports you are sure cannot enable arbitrary code execution, and if you are unsure, the README suggests opening an issue so the team can review and possibly extend the allowlist. That is a maintainer-mediated process, not a local policy engine.

There is a second, subtler cost. Because the hook patches pickle globally, it applies to every unpickling operation in the process, not just model loading. That is what makes it effective and also what makes it invasive: a library that unpickles its own cache files will now run through the same check. The README documents deactivate_safe_ml_environment for turning it off, and notes that the check_safety context manager restores whatever hook was installed when it was entered, so nesting does not silently unhook an outer guard. That detail matters in a codebase where several components install guards.

The severity vocabulary is coarse. The README shows OVERTLY_MALICIOUS as one value. There is no published list of the other levels in the README, so a caller that wants to treat a medium-severity finding differently from a high one has to inspect what the library actually returns rather than assume a documented scale.

## Fickling as an offensive tool, and why that is deliberate

The README states plainly that you can use Fickling to create malicious pickle files, and it ships the command for it: fickling --inject "print('Malicious')" file.pkl > malicious.pkl writes a pickle that runs the injected code on load. It also documents PyTorch polyglots, files that are validly interpreted as more than one format, and supports identifying, inspecting and creating them across several PyTorch file formats. The example directory includes inject_mobilenet.py, inject_pytorch.py, numpy_poc.py and pytorch_poc.py, which is consistent with that framing.

This is a defensible choice and a real one to weigh. A detector is only as good as the samples it is tested against, and a tool that can build the thing it detects lets a team write its own test corpus instead of waiting for one. The trade-off is that Fickling is dual-use in the literal sense, and any policy that governs what tooling is allowed on a workstation has to account for a package whose documented CLI includes a code-injection flag. If your environment forbids that, the analysis half of Fickling is still usable, but you are adopting a package that ships the other half.

## Where Fickling is the wrong tool

Fickling analyzes pickle. If your model format is safetensors, ONNX or a plain JSON config, it has nothing to inspect, and the safe ML environment hook will not protect you from a format that does not go through pickle at all. The allowlist approach is also a poor fit for a pipeline that legitimately needs broad imports: every addition to also_allow is a decision you now own, and a long also_allow list erodes the guarantee to the point where you are maintaining a denylist with extra steps.

The static analysis is heuristic, not a proof. The README's own example verdict leans on an unused variable as evidence, which is a good signal and not a soundness argument. A pickle that assigns the result of an eval to a variable it later uses would not trip that particular pattern. Treat a clean result as a reason to keep looking, not as a certificate.

Finally, Fickling does not remove pickle from your stack. It wraps it, hooks it, and reads it. If your goal is to stop deserializing untrusted objects entirely, the answer is a format that cannot express a callable, and Fickling is the tool you use on the files you have not migrated yet.

## Maintenance, licensing and upgrade cost

The repository is not archived, and the last push was on 2026-09-10. Recent releases are v0.1.10 on 2026-03-13, v0.1.11 on 2026-05-06 and v0.1.12 on 2026-06-26, so the release cadence is steady but the version numbers are still 0.1.x. pyproject.toml classifies the project as Development Status 4 - Beta. Plan for the API to move.

Fickling is licensed under LGPL-3.0, and pyproject.toml carries the classifier GNU Lesser General Public License v3 or later (LGPLv3+). That is a copyleft licence with a linking exception, which is a different proposition from MIT or Apache-2.0 for a library you embed in a shipped product. How the LGPL applies to your distribution depends on whether you modify Fickling and how you link it, and that is a question for your own counsel rather than something to settle from a README.

The dependency picture is the low-cost part. The base install needs almost nothing, so adding Fickling to a CI job that scans model artifacts is cheap. The torch extra is where the weight appears: pyproject.toml pins torch >= 2.1.0, torchvision >= 0.24.1, and numpy with version bounds that differ between Python 3.10 and 3.11 and above. If you only need the CLI safety check, install the base package and leave the torch extra out. The archive extra adds py7zr for 7z support, pinned to exclude the yanked 1.1.2 release, which tells you the maintainers are tracking upstream packaging problems rather than ignoring them. Python support runs from 3.10 upward per requires-python, while the README's prose says the project has been tested on 3.9 through 3.13; the two statements do not agree, and the packaging metadata is the one that governs installation.

## Conclusion

Adopt Fickling if you load pickle or PyTorch files from outside your own build pipeline, or if you need to read what a suspicious .pkl actually does before running it. Do not adopt it as a general-purpose serializer or as a replacement for pickle in code you control end to end; it hooks and analyzes pickle rather than replacing it, and the PyTorch polyglot and archive paths pull in the torch and py7zr extras. Before relying on it in production, read the allowlist in the safe ML environment hook, confirm that activate_safe_ml_environment() is called before torch or numpy is imported, and check the LGPL-3.0 terms against how you ship the library.

## FAQ

### What is Fickling and what does it do?

Fickling is a decompiler, static analyzer and bytecode rewriter for Python pickle object serializations, usable as both a Python library and a CLI. It can detect, analyze, reverse engineer, or even create malicious pickle and pickle-based files, including PyTorch files.

### How do I install Fickling?

Install it from PyPI with python -m pip install fickling, or uv pip install fickling. The pytorch and polyglot modules need the torch extra, installed as python -m pip install fickling[torch].

### Can Fickling check a pickle file without loading it?

Yes. The CLI command fickling --check-safety -p pickled.data performs a safety check on a pickle file without loading it, and the library offers fickling.is_likely_safe for the same purpose in Python.

### Does Fickling work with PyTorch model files?

PyTorch is an optional dependency, and the README states that Fickling supports identifying, inspecting and creating polyglots across several PyTorch file formats. Using those modules requires installing the torch extra.

### What happens when Fickling detects an unsafe pickle?

The safety checks hook the pickle library so that loading a pickle file raises an UnsafeFileError exception if malicious content is detected. The exception exposes the analysis details through e.info, including a severity value and the specific findings.

### What license is Fickling released under?

Fickling is licensed under LGPL-3.0, and pyproject.toml records the classifier GNU Lesser General Public License v3 or later (LGPLv3+).

## Sources

- [Issues](https://github.com/trailofbits/fickling/issues)
- [License: LGPL-3.0](https://github.com/trailofbits/fickling/blob/master/LICENSE)
- [README](https://github.com/trailofbits/fickling/blob/master/README.md)
- [Releases](https://github.com/trailofbits/fickling/releases)
- [trailofbits/fickling on GitHub](https://github.com/trailofbits/fickling)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/trailofbits-fickling
