What Is a .PKL File?

Python pickle

📂Data
🏷️.pkl
🎯application/octet-stream

Python Pickle File (.pkl, .pickle)

Overview

Pickle is Python's native object serialisation format. It converts almost any Python object - nested dictionaries, class instances, NumPy arrays, trained models, functions by reference - into a byte stream that can be written to disk and reconstructed later as an equivalent object.

Its convenience is also its danger. Unpickling is not parsing: it is a small stack-based virtual machine that executes opcodes, and those opcodes can import modules and call functions. Loading a pickle file from an untrusted source is equivalent to running arbitrary code. This is the single most important thing to know about the format.

Technical Specifications

Format Details

  • MIME Type: application/octet-stream
  • File Extensions: .pkl, .pickle, .p, sometimes .sav or .joblib
  • Category: Data
  • Structure: a stack-based opcode stream
  • Protocol versions: 0 through 5
  • Module: pickle in the Python standard library

Protocol versions

Protocol Introduced Notes
0 Original ASCII, human-readable, very verbose
1 Original Binary form of protocol 0
2 Python 2.3 Efficient new-style class support
3 Python 3.0 bytes support; not readable by Python 2
4 Python 3.4 Large objects, more compact; default since 3.8
5 Python 3.8 Out-of-band data buffers for zero-copy

Identification

Protocol 2 and later begin with the PROTO opcode:

80 04    →  protocol 4
80 05    →  protocol 5

Protocol 0 is ASCII and looks like a sequence of short commands. Every pickle ends with the . opcode (0x2E), the STOP instruction.

Because there is no strong magic number, a .pkl file is best identified by disassembling the opcode stream rather than by signature matching alone.

How Pickle Works

The format is a program for a tiny stack machine. pickletools.dis shows this clearly:

import pickle, pickletools

data = {"name": "Ada", "scores": [1, 2, 3]}
blob = pickle.dumps(data, protocol=4)
pickletools.dis(blob)

Output (abbreviated):

    0: \x80 PROTO      4
    2: \x95 FRAME      54
   11: }    EMPTY_DICT
   12: \x94 MEMOIZE
   13: (    MARK
   14: \x8c     SHORT_BINUNICODE 'name'
   22: \x94     MEMOIZE
   23: \x8c     SHORT_BINUNICODE 'Ada'
   ...
   52: u        SETITEMS  (MARK at 13)
   53: .    STOP

The opcodes push values, build containers, and - critically - GLOBAL and REDUCE can import a module and call a callable. That is the mechanism the format needs to reconstruct class instances, and also the mechanism an attacker exploits.

Security: the central concern

A malicious pickle does not need to be complicated:

# Illustrative only - this is what a hostile payload looks like
class Exploit:
    def __reduce__(self):
        import os
        return (os.system, ("whoami",))

Pickling an Exploit instance produces a file that runs whoami the moment anyone calls pickle.load on it. No warning, no sandbox, no opt-in.

Practical guidance

  • Never unpickle data from an untrusted or unauthenticated source. There is no safe subset and no validating parser that makes this safe.
  • Do not accept pickles over a network boundary, including model files downloaded from public model hubs. This is a live supply-chain risk: several model registries scan uploads specifically for hostile pickles.
  • Prefer safer formats for interchange: JSON for plain data, Parquet for tables, NPY/NPZ for arrays, ONNX or safetensors for models.
  • If you must accept pickles, sign them and verify the signature before loading, or restrict unpickling with a custom Unpickler that overrides find_class to an explicit allowlist:
import pickle, io

ALLOWED = {("collections", "OrderedDict"), ("builtins", "dict")}

class RestrictedUnpickler(pickle.Unpickler):
    def find_class(self, module, name):
        if (module, name) not in ALLOWED:
            raise pickle.UnpicklingError(f"blocked: {module}.{name}")
        return super().find_class(module, name)

def safe_loads(data: bytes):
    return RestrictedUnpickler(io.BytesIO(data)).load()

Even this is a mitigation, not a guarantee. Treat it as defence in depth rather than a licence to load hostile input.

Common Use Cases

  • Caching computed results: memoising expensive work between runs.
  • Machine learning models: scikit-learn's documented persistence path, and the basis of joblib.
  • Inter-process communication: multiprocessing pickles objects to move them between workers.
  • Session and application state: saving a program's state to resume later.
  • Celery and task queues: pickle was historically a default serialiser (now discouraged in favour of JSON).

How to Open a Pickle File

Loading

import pickle

with open("model.pkl", "rb") as f:
    obj = pickle.load(f)

print(type(obj))

Saving

import pickle

with open("model.pkl", "wb") as f:
    pickle.dump(obj, f, protocol=pickle.HIGHEST_PROTOCOL)

Inspecting without executing

This is the right first step for any file you did not create:

import pickletools

with open("unknown.pkl", "rb") as f:
    pickletools.dis(f)      # disassembles; does not execute

Look for GLOBAL or STACK_GLOBAL opcodes referencing os, subprocess, builtins.eval, or builtins.exec. Their presence in what should be a plain data file is a strong signal of tampering.

# From the shell
python -m pickletools unknown.pkl | head -40

# Quick check for suspicious references without running anything
strings unknown.pkl | grep -Ei 'os|subprocess|eval|exec|system'

With pandas and scikit-learn

import pandas as pd

df = pd.read_pickle("frame.pkl")
df.to_pickle("frame.pkl")
import joblib

joblib.dump(model, "model.joblib")     # more efficient for large NumPy arrays
model = joblib.load("model.joblib")    # same security caveats apply

Advantages

  • Handles almost any Python object: including nested and custom types.
  • No schema required: nothing to define in advance.
  • Preserves object identity: shared and cyclic references are reconstructed correctly.
  • Fast: binary protocols 4 and 5 are efficient.
  • Standard library: no dependency.

Limitations

  • Fundamentally unsafe to load from untrusted input: the defining drawback.
  • Python-only: no meaningful cross-language support.
  • Version-fragile: a pickle referencing a class that has since moved or changed will fail or silently misbehave.
  • Not human-readable in the protocols anyone actually uses.
  • Poor long-term archival: depends on your code remaining importable and unchanged.
  • No schema or validation: nothing describes what a file should contain.
  • NPY and NPZ: safe binary storage for numeric arrays.
  • PARQUET: the right choice for tabular data.
  • JSON: safe, portable, human-readable, for plain data.
  • ONNX: a declarative, non-executable model format.
  • PYTORCH: PyTorch checkpoints, which are pickle-based and carry the same risk.

File Information

File Description

Python pickle

Category

Data

Extensions

.pkl, .pickle

MIME Type

application/octet-stream

Related File Types

Other file types in the Data category you might also need:

Start Analyzing PICKLE Files Now

Use our free AI-powered tool to detect and analyze Python pickle files instantly with Google's Magika technology.

Try File Detection Tool