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.

How to identify .PKL files from the bytes

Python pickle files carry a fixed byte pattern, the file signature or magic number, that identifies them regardless of the extension. This is what our detector reads.

OffsetHexASCII
080 04Protocol 4, the default since Python 3.8.
080 05Protocol 5.

Check a file yourself: xxd -l 16 file.pkl on macOS and Linux, or Format-Hex file.pkl | Select-Object -First 1 in PowerShell.Full PICKLE signature reference

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