CLI tool
trailofbits/fickling avatar
trailofbits/fickling

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

A Python pickling decompiler and static analyzer

669 stars76 forksPythonLGPL-3.0

At a glance

What is it?
Fickling turns pickle bytecode into readable Python source and hooks the pickle module to block unsafe deserialization at load time. It is aimed at engineers who handle untrusted .pkl or PyTorch model files, and its main limits are the allowlist model and the LGPL-3.0 licence.
Who is it for?
Adopt Fickling if you load .pkl or PyTorch artifacts from outside your own build pipeline, and start with fickling --check-safety -p on the files you already have. Skip it if you need a general-purpose sandbox for arbitrary Python, because the hook only covers pickle deserialization.
Can I use it commercially?
Yes, with conditions. LGPL-3.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
Is it still maintained?
Yes. The repository last received commits 6 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The problem Fickling addresses: pickle is a program, not a data format

A pickle file is not inert. The pickle virtual machine executes opcodes, and the GLOBAL and REDUCE opcodes let a file name a callable and invoke it during unpickling. That means opening an untrusted .pkl is closer to running a script than parsing JSON. The README states the project's scope plainly: Fickling is a decompiler, static analyzer, and bytecode rewriter for Python pickle object serializations, and it can be used to detect, analyze, reverse engineer, or even create malicious pickle or pickle-based files, including PyTorch files. The audience is therefore narrow and specific: security engineers reviewing model artifacts, ML platform teams who accept uploads from outside their organization, and incident responders who need to read a suspicious pickle without executing it. The repository topics list machine-learning, python and security, which matches that framing. If your pickles are produced and consumed entirely inside one build you control, the tool has less to offer you.

How the analysis works: pickle bytecode becomes a Python AST

The core abstraction is visible in the decompilation example. Fickling exposes a Pickled class, and Pickled.load takes a serialized pickle and produces an object whose .ast attribute is a standard Python ast.Module. In the README example, pickling the list [1, 2, 3, 4] and dumping fickled_object.ast with ast.dump yields a Module containing a single Assign to a Name called result, with a List of four Constant nodes. So the pickle stack machine is lifted into Python syntax, and from there the safety analysis can reason about calls, variable assignments and dead stores in ordinary AST terms. That is also how the reported findings are phrased. The sample UnsafeFileError payload shows an analysis string that names eval(b'[5, 6, 7, 8]') and then notes that _var0 is assigned that value but unused afterward, which the message calls suspicious. The detailed_results dictionary keys the same evidence as OvertlyBadEval and UnusedVariables. The severity field in that example is OVERTLY_MALICIOUS. This design explains both the tool's strength and its blind spot: it reasons about the shape of the decompiled program, not about what the called functions actually do at runtime.

Runtime enforcement: hooking pickle instead of scanning files offline

Fickling offers four documented ways to enforce safety, and they differ in how much of the process they touch. The recommended option is fickling.always_check_safety(), which the README says enforces checks every time pickle is used to deserialize; a subsequent pickle.load on a bad file raises fickling.UnsafeFileError. Option two wraps the same behaviour in a context manager, with fickling.check_safety(). The README is explicit that files loaded outside the context are not checked, and that on exit the manager restores whatever was installed when it was entered, so a pre-existing hook stays in effect rather than pickle being left unhooked. Option three is fickling.load("file.pkl") as a drop-in for pickle.load on a single file, and option four is fickling.is_likely_safe("file.pkl"), which performs the check without loading. That fourth function name is worth reading carefully: the word likely is the project's own hedge, and it signals that this is a heuristic classifier rather than a proof. For non-Python callers the README gives the CLI equivalent, fickling --check-safety -p pickled.data. The exception object carries the structured result, so a caller can branch on severity rather than just catching and discarding.

The ML allowlist hook and its operational cost

The AI/ML path is a separate mechanism from the safety checker. fickling.hook.activate_safe_ml_environment() installs global hooks on pickle, and according to the README it verifies imports made when loading a model, checking them against an allowlist of imports from ML libraries considered safe and blocking files that contain other imports. The placement instruction is strict: the call must go as early as possible, before importing torch, numpy, or any other library that uses pickle. That ordering requirement is the practical failure mode. If torch is imported first, the hook is installed after the library has already captured its pickle references, and the protection is not what you think it is. The escape hatch is the also_allow argument, which takes a list of dotted import strings such as some.import. The README warns that manually added imports must actually be safe and must not let attackers execute arbitrary code, and offers to review proposed additions via a GitHub issue. Treat also_allow as a security decision, not a compatibility knob. Deactivation is fickling.hook.deactivate_safe_ml_environment().

Tracing, injection and PyTorch polyglots

Two CLI modes cover the analysis work that does not fit a load-time hook. fickling --trace file.pkl lets you follow the pickle virtual machine without exercising malicious code, which is the mode to reach for when you want to see what a file would do rather than whether it is flagged. fickling --inject "print('Malicious')" file.pkl > malicious.pkl does the reverse: it writes a pickle that runs the supplied code on every load. The README presents injection as a capability, and it is genuinely useful for building test fixtures and for confirming that a detection rule fires on known-bad input. It is also a reminder that the rewriter is symmetric, so a copy of Fickling in the wrong hands is a payload generator. The PyTorch section covers polyglots, files the README describes as validly interpretable as more than one file format, and states that Fickling supports identifying, inspecting and creating polyglots across PyTorch file formats. The README's list of those formats is truncated in the material available here, so which formats beyond PyTorch v0.1.1 are covered cannot be confirmed from this text. PyTorch itself is an optional dependency: the pytorch and polyglot modules require installing fickling[torch].

Where Fickling stops being the right tool

The hook protects pickle deserialization. It does not sandbox Python. Any code path that reaches eval, exec, importlib or a subprocess without going through pickle is outside its scope, and the README's own example of a bad file is one where the malicious call is reached via the pickle opcode stream. A second limit is the nature of the verdict. The analysis output pairs a severity label with a human-readable explanation, and the unused-variable heuristic is exactly that, a heuristic. Legitimate serializers can produce pickles with imports outside the ML allowlist, which is why also_allow exists; conversely, a file that passes the allowlist check has not been proven benign, only shown to import from a set the project considers acceptable. Third, the enforcement options have different reach, and mixing them is easy to get wrong: always_check_safety is process-wide, check_safety is scoped, and files loaded outside a context manager are not checked at all. Finally, Fickling is a Python tool. If your ingestion pipeline is written in Go or Rust, the README's answer is to shell out to the CLI for the safety check, which adds a process boundary and a Python runtime to your deployment.

Alternatives and the difference in approach

The obvious comparison is to not unpickle at all. Formats such as safetensors were designed so that loading a model never executes code, which removes the attack surface rather than analyzing it. That is a different trade: you must convert your artifacts, and any model that genuinely needs pickle semantics cannot move. Fickling's position is that you often cannot avoid pickle, because the ecosystem ships it, so the tool meets the format where it is and adds a check at the boundary. A second comparison is to running untrusted deserialization inside a container or a seccomp sandbox. That approach contains the damage regardless of what the pickle does, including paths Fickling does not model, but it costs you an isolation layer and makes the failure mode a crash rather than a clear UnsafeFileError with a severity field. The two are complementary, and the README does not claim otherwise. Within the static-analysis space, Fickling's distinguishing choice is decompiling to a real Python AST rather than pattern-matching opcode bytes, which is what allows findings like unused variables to be expressed at all.

Install, maintenance and licence

Installation is a single pip or uv command: python -m pip install fickling, or uv pip install fickling. The README states Fickling has been tested on Python 3.9 through 3.13 and has very few dependencies. Adding the PyTorch support means python -m pip install fickling[torch] or uv pip install fickling[torch]. On the maintenance side, the release history shows v0.1.12, v0.1.11 and v0.1.10 spaced roughly six to eight weeks apart through 2026, and the repository is not archived. The version numbers are still in 0.1.x, which is worth weighing if you plan to depend on the exception payload structure: the README documents severity, analysis and detailed_results keys, but a pre-1.0 library has no compatibility promise attached to them. The licence is LGPL-3.0. If you link Fickling into your own application rather than invoking the CLI, the copyleft terms of that licence are a factor in how you distribute, and this article cannot tell you whether your specific use triggers them. That is a question for your own legal review, not for the README.

Editorial conclusion

Adopt Fickling if you load .pkl or PyTorch artifacts from outside your own build pipeline, and start with fickling --check-safety -p on the files you already have. Skip it if you need a general-purpose sandbox for arbitrary Python, because the hook only covers pickle deserialization. Before rolling it out, verify that your own model imports pass the ML allowlist and decide whether the LGPL-3.0 terms fit how you ship.

Official sources

  1. Issues
  2. License: LGPL-3.0
  3. README
  4. Releases
  5. trailofbits/fickling on GitHub
Community notes

Community notes