Abstract

The load-time attack surface established by the artifact threat model has a concrete, dominant instance: object-serialization formats that execute code during deserialization. This article examines deserialization and loader attacks at a conceptual, defensive level. It explains why certain serialization mechanisms run arbitrary code when a file is loaded, why this makes an ordinary-looking checkpoint a remote-code-execution vector, why the safetensors format eliminates the code path by storing only tensor data, and what residual risks remain in custom loaders and surrounding code. Every point is paired with a defense, and all illustrations are sanitized pseudocode with no working exploit. The key takeaway is that safe deserialization is a hard requirement, not a preference: prefer data-only formats that cannot execute code, refuse code-executing formats from untrusted sources, and audit custom loaders for the paths that reintroduce the risk.

The vulnerability at the heart of model-loading is not exotic; it is a decades-old class of bug wearing a machine-learning costume. Some serialization formats do not merely store data — they store a recipe for reconstructing objects, and executing that recipe runs code. When the recipe comes from an untrusted file, the code that runs is the file author's choice, executed in your process the instant you load. For general software this is the well-known insecure-deserialization vulnerability; for machine learning it arrives disguised as a model checkpoint, so a developer who would never run a stranger's script happily loads a stranger's weights and runs the stranger's code anyway. Understanding why loading can execute code is the first step to never letting it.

Why deserialization can execute code

Serialization turns in-memory objects into bytes; deserialization turns those bytes back into objects. For simple data — numbers, strings, arrays — this is a faithful, safe copy. But some serialization mechanisms are designed to reconstruct arbitrary objects, including objects whose reconstruction requires running code, such as invoking a constructor or a special reconstruction hook. When such a format deserializes a file, it does whatever the file says to rebuild the objects, and a maliciously crafted file can say 'run this code' as part of that rebuilding.

The Python pickle format is the canonical example in machine learning, because many model artifacts have historically been distributed as pickled objects. Pickle's design includes an object-reconstruction hook that a crafted file can use to execute arbitrary code during unpickling. The mechanism is not a bug in pickle — it is its documented behavior — which is exactly why it is dangerous: loading a pickled file is, by design, capable of running code chosen by whoever created the file.

The defensive principle is that any deserialization of untrusted data using a code-capable format is a code-execution vulnerability. This is a general truth of software security, and the machine-learning corollary is that loading an untrusted checkpoint in such a format is remote code execution. The fix is not to sanitize the file but to not use a code-capable format for untrusted data at all.

A crafted checkpoint's reconstruction hook runs the author's code during deserialization. Loading executes the payload Attacker crafts artifact Developer loads it Deserializer reconstructs Code runs in the process publish load execute
A crafted checkpoint's reconstruction hook runs the author's code during deserialization.
🛡️ Countermeasures
  • Never deserialize untrusted data with a code-capable format; that is a code-execution vulnerability by design.
  • Recognize that loading an untrusted checkpoint in such a format is remote code execution.
  • Fix by choosing a data-only format, not by attempting to sanitize a code-capable one.

The exploit shape, sanitized

It helps to understand the exploit's shape without any working payload. A code-capable serialization format lets the serialized file specify, alongside the data, a callable to invoke during reconstruction. An attacker crafts a file whose reconstruction step names a dangerous callable and its arguments, so that deserializing the file invokes that callable with those arguments. The victim never sees a script; they see a model file, and the code runs as a side effect of loading it. The sanitized sketch below shows only the structure — a reconstruction hook that would invoke some callable — with no real payload.

The severity comes from context. Loading typically happens in trusted, privileged environments: a developer's workstation with cloud credentials, a CI runner with deploy keys, a serving process with database access. Code that runs at load time inherits all of that, so a single loaded malicious checkpoint can exfiltrate secrets, pivot through the network, or establish persistence, all before the model is used. This is why the load step, not inference, is the primary concern for untrusted artifacts.

Crucially, no amount of inspecting the model's weights protects against this, because the payload lives in the serialization structure, not the tensors. Scanning tools can detect some known-bad patterns in code-capable files, and they raise the bar, but they are a mitigation for a format that should not be used for untrusted data in the first place. The structural fix is to remove the code path.

# SANITIZED ILLUSTRATION — shows WHY loading can run code.
# This is structure, not a runnable exploit.
class MaliciousObject:
    def __reconstruct__(self):
        # A code-capable format invokes a callable here during load.
        # An attacker would name a dangerous callable + args.
        return (SOME_CALLABLE, (ARGS,))   # <-- placeholder, no real payload

# Loading a file containing such an object INVOKES the callable.
# Defense: use a data-only format so no callable is ever invoked.
Sanitized STRUCTURE only — a reconstruction hook; NO working payload.
⚠️
The payload is in the wrapper. The malicious code lives in the serialization format's reconstruction step, not in the tensors, so inspecting the weights reveals nothing — only avoiding the code-capable format removes it.
🛡️ Countermeasures
  • Understand that the payload lives in the serialization structure, not the weights, so weight inspection cannot catch it.
  • Treat scanners for code-capable files as a bar-raiser, not a substitute for a data-only format.
  • Assume load-time code inherits the privileged context of the loader; contain it with sandboxing.

Why safetensors eliminates the code path

The structural fix is a serialization format that stores only data and has no object-reconstruction mechanism. The safetensors format was designed for exactly this: it stores tensors as a header describing shapes and types plus a block of raw tensor bytes, with no facility to invoke callables or reconstruct arbitrary objects. Loading a safetensors file reads numbers into arrays and does nothing else — there is no code path for an attacker to hijack, so a malicious safetensors file cannot execute code on load.

This is the difference between a mitigation and an elimination. Scanning a code-capable file for bad patterns is a mitigation that can miss novel payloads; using a format with no code path is an elimination that removes the entire vulnerability class. For the load-time code-execution risk, safetensors (or any equivalently data-only format) converts the load step from an execution surface into a pure data read, which is the strongest possible defense.

The important caveat is scope: a safe format eliminates the load-time code-execution risk, not every artifact risk. The weights loaded from a safetensors file can still be backdoored (a data-poisoning concern), and the surrounding config or loader code can still be malicious. So safetensors is necessary and highly effective for the deserialization attack specifically, and it must be paired with integrity verification and behavioral testing for the risks it does not address.

A data-only format has no reconstruction hook to hijack, eliminating the load-time code path. Code-capable versus data-only code-capable data-only Code-capable format reconstruction hook Runs code on load RCE vector Data-only format tensors only No code path pure data read vs
A data-only format has no reconstruction hook to hijack, eliminating the load-time code path.
🛡️ Countermeasures
  • Prefer a data-only format (e.g., safetensors) that has no object-reconstruction mechanism to hijack.
  • Treat a safe format as eliminating the load-time code class, not merely mitigating it.
  • Pair the safe format with integrity verification and backdoor testing for the risks it does not cover.

Residual risk in custom loaders

Choosing a safe format is necessary but not automatically sufficient, because the code around the load can reintroduce the risk. A custom loader that, for convenience, falls back to a code-capable format when the safe one is absent hands the attacker the very path you tried to close. A loader that executes code referenced by the config, that imports modules named in the artifact, or that runs a bundled script 'to set up the model' creates new load-time code paths outside the tensor format. The vulnerability moves from the format to the loader.

This is a common pattern: a repository ships a model with custom loading code that must be trusted and run to use the model, and a developer runs it because the tooling encourages it. Running arbitrary loader code from an untrusted repository is the same risk as deserializing a code-capable file — untrusted code executing in your process — just relocated. So the defense must cover the whole loading path, not only the tensor format: refuse to run untrusted loader code, disable code-capable fallbacks, and validate config so it cannot point the loader at attacker-controlled code.

The auditing discipline is to trace every code path the load can trigger and ensure none of them execute untrusted content. Prefer loaders that only read data; where custom loading is unavoidable, review and sandbox it exactly as you would any untrusted dependency. The format is the biggest single lever, but the loader is where a closed door can be quietly reopened.

A safe format is not enough if the loader runs untrusted code around it. Is this load path safe? Load a checkpoint from a source Data-only format? no code path Loader runs code? config/scripts Untrusted code path refuse or sandbox Safe read data only yes no yes no
A safe format is not enough if the loader runs untrusted code around it.
🛡️ Countermeasures
  • Disable code-capable format fallbacks so an absent safe file cannot silently reopen the code path.
  • Refuse to run untrusted loader code, bundled scripts, or config-referenced modules.
  • Audit every code path the load can trigger and sandbox any unavoidable custom loading.

Defending the load

The controls compose into a safe-loading discipline. Use data-only formats exclusively for untrusted checkpoints, with code-capable formats forbidden from untrusted sources and their fallbacks disabled. Verify artifact integrity and authenticity so a substituted or tampered file is caught. Refuse to execute untrusted loader code, and validate config so it cannot redirect the loader. And load in a sandboxed, least-privilege, egress-denied environment so that if any control is bypassed — or a code-capable format is genuinely unavoidable — the blast radius is contained rather than catastrophic.

The unifying principle is that safe deserialization is the standard software defense against insecure deserialization, applied to model artifacts. Nothing here is machine-learning-specific except the disguise: the checkpoint looks like data, so the code-execution risk hides behind a load call that feels benign. Removing the code path (safe format), refusing untrusted code (loader discipline), and containing the load (sandbox) are the same layered defenses used for any untrusted deserialization.

The synthesis, consistent with NIST's secure-development and supply-chain guidance and the model-supply-chain framing of Gu and colleagues, is that loading a checkpoint must never be allowed to execute untrusted code. Prefer data-only formats that eliminate the code path, forbid code-capable formats and fallbacks for untrusted artifacts, audit custom loaders for reintroduced paths, and sandbox the load. Do that, and the deserialization attack — the sharpest instance of the artifact threat — is closed at its root.

🛡️ Countermeasures
  • Compose data-only formats, integrity verification, refusal of untrusted loader code, and sandboxed loading.
  • Apply standard insecure-deserialization defenses to model artifacts, which the load-as-data disguise obscures.
  • Never allow loading a checkpoint to execute untrusted code, by format and by loader discipline together.

Why this matters for agents

Agent pipelines load many artifacts automatically — base models, adapters, embedding models — often from custom repositories with bespoke loading code, in CI and at service startup. Each automatic load in a code-capable format, or each execution of untrusted loader code, is a chance for a malicious artifact to run code in the agent's privileged infrastructure before the agent does anything. The convenience of one-line model loading is exactly the convenience that makes this attack land.

The organizing lesson is that safe deserialization is non-negotiable infrastructure hygiene for agent systems: standardize on data-only formats, forbid code-capable ones and their fallbacks for untrusted sources, refuse to run untrusted loader code, and sandbox every load. NIST's guidance and the model-supply-chain literature say the same thing to agent builders as classic application security has always said — deserializing untrusted data with a code-capable format is remote code execution, so do not do it, no matter how much the tooling makes it look like just reading a file.

🛡️ Countermeasures
  • Standardize agent pipelines on data-only formats and forbid code-capable formats and fallbacks from untrusted sources.
  • Refuse untrusted loader code and sandbox every automatic model load in the agent stack.

Key takeaways

  • Some serialization formats reconstruct objects by running code, so deserializing an untrusted file in such a format is remote code execution.
  • Pickle is the canonical machine-learning example: its object-reconstruction hook lets a crafted checkpoint run code during loading, by design.
  • The payload lives in the serialization structure, not the weights, so inspecting the tensors cannot detect it.
  • The safetensors format eliminates the code path by storing only tensor data with no reconstruction mechanism — an elimination, not a mitigation.
  • A safe format does not cover backdoored weights, malicious config, or custom loaders that run untrusted code, which can reintroduce the risk.
  • Defend by using data-only formats exclusively for untrusted artifacts, disabling code-capable fallbacks, refusing untrusted loader code, and sandboxing the load.

Practitioner Toolkit

Copy-paste, strictly defensive artifacts you can use today. Nothing here attacks a real system.

Safe-deserialization review gatechecklist

Run before loading any externally-sourced checkpoint.

  • Only data-only formats are used for untrusted checkpoints.
  • Code-capable formats are forbidden from untrusted sources and their fallbacks disabled.
  • Artifact integrity and authenticity are verified before loading.
  • Untrusted loader code, bundled scripts, and config-referenced modules are refused.
  • Config is validated so it cannot redirect the loader to untrusted code.
  • The load runs sandboxed, least-privilege, with egress denied.
🔒Deserialization safety policypolicy

Illustrative loader policy for model artifacts.

deserialization_policy:
  formats:
    allowed_for_untrusted: [data_only]   # e.g. safetensors
    code_capable: forbidden_from_untrusted
    code_capable_fallback: disabled
  loader:
    run_untrusted_loader_code: false
    execute_bundled_scripts: false
    config_referenced_imports: blocked
  integrity:
    verify_signature: required
  sandbox:
    least_privilege: true
    egress: deny
Example policy snippet — adapt to your stack.
🧪Format-and-loader pre-check probeharness

Sanitized skeleton that gates format and loader safety before loading (defensive).

# DEFENSIVE PRE-CHECK — refuse unsafe load paths
function safe_deserialize(artifact):
    assert format_is_data_only(artifact), "code-capable format"
    assert not has_custom_loader_code(artifact), "untrusted loader code"
    assert config_has_no_code_refs(artifact), "config points at code"
    assert verify_signature(artifact, trusted_keys), "bad signature"
    return True   # only then load, in a sandbox with egress denied
Mock pre-check — refuses code-capable formats and untrusted loader code.
🚀Minimum viable deserialization defensequickstart

Do these first if you load external checkpoints.

  • Use data-only formats exclusively for untrusted checkpoints.
  • Forbid code-capable formats and disable their fallbacks.
  • Refuse to run untrusted loader code or bundled scripts.
  • Sandbox the load with least privilege and egress denied.

Glossary

Serialization
Converting in-memory objects into bytes for storage or transport.
Deserialization
Reconstructing objects from serialized bytes; in code-capable formats this can run code chosen by the file author.
Insecure deserialization
The vulnerability class where deserializing untrusted data with a code-capable format executes attacker code.
Pickle
A Python serialization format with an object-reconstruction hook that a crafted file can use to execute code on load.
Reconstruction hook
A serialization mechanism that invokes a callable to rebuild an object, which an attacker can point at dangerous code.
safetensors
A data-only tensor serialization format with no object-reconstruction path, eliminating load-time code execution.
Custom loader
Bespoke code that loads a model, which can reintroduce code-execution risk via fallbacks, config, or bundled scripts.
Sandboxed load
Loading in an isolated, least-privilege, egress-denied environment so a malicious artifact's impact is contained.

References

  1. Gu et al., BadNets: Identifying Vulnerabilities in the Machine Learning Model Supply Chain (arXiv 1708.06733)
  2. NIST SP 800-218, Secure Software Development Framework (SSDF)
  3. NIST SP 800-161r1, Cybersecurity Supply Chain Risk Management Practices for Systems and Organizations
  4. NIST AI 100-2 e2023, Adversarial Machine Learning: A Taxonomy and Terminology
  5. safetensors format specification (Hugging Face)
  6. MITRE ATLAS (Adversarial Threat Landscape for AI Systems)