Code Execution in Data Pipelines: When Loading a File Runs Someone Else’s Code

Code Execution in Data Pipelines: When Loading a File Runs Someone Else's Code

In July 2026, Hugging Face disclosed an intrusion that reached its infrastructure through the pipeline that processes uploaded datasets. A maliciously shaped dataset abused two paths at once, a remote code dataset loader and a template injection in a dataset configuration, to run code on a processing worker just by being processed. That is the plainest example you will find of code execution in data pipelines: nobody popped a shell or chained a memory bug, they handed the system a file and let the system read it.

Why “it is only data” is false comfort

For a system built to ingest data, processing untrusted data is executing untrusted code. The two ideas feel separate. Data is inert, code runs, and a file you have not opened cannot hurt you. That intuition is where the trouble begins. The moment a loader reads a file, it makes choices based on the bytes inside, and a rich enough format lets the file steer those choices all the way into a running process. The gap between “loading” and “running” is not a wall. Often it is not even a line.

Picture an invented service, Acme Data, that lets anyone upload a dataset and shows a quick preview. A user uploads a file. A background worker downloads it, deserializes it, renders a few fields for the preview, and sometimes fetches a loader script the dataset points at. Every one of those steps reads bytes the uploader controls. If any single step can be steered into running instructions from the file, the attacker has code execution before a human ever glances at the preview. The upload button is the entry point, and the worker is the target.

For a system built to ingest data, loading a file is the act of running whatever that file decided you should run.

Three ways code execution in data pipelines actually happens

The same underlying mistake shows up in three familiar shapes. Each one lives at the ingestion moment, when a loader first touches an artifact it did not create.

1. Unsafe deserialization on load

Some file formats are not just data, they are a small program that rebuilds an object. Python’s pickle is the clearest case. A pickle file can carry instructions that run the instant it is deserialized, so loading a pickle based model or dataset file hands the file author a callback straight into your process. No preview, no click, no second step. The call to load is the exploit.

# unsafe: pickle runs code the moment it loads
import pickle
model = pickle.load(open("model.pkl", "rb"))

# safer: safetensors only reads tensors, no execution path
from safetensors.torch import load_file
weights = load_file("model.safetensors")

This is the deserialization class applied at ingestion. We cover the full mechanism in our deeper dive on insecure deserialization. The point for a pipeline is narrower: if a format can encode behaviour, then reading it is running it, and a loader that accepts that format from strangers is a loader that runs strangers’ code.

2. Template injection in a dataset or config field

Loaders often render fields rather than copy them. A dataset config might name a split, build a file path from a pattern, or carry a description that the loader passes through a template engine to produce a final value. If a value inside the uploaded data reaches that engine as the template itself, the attacker is now writing the template. A field that reads {{ 7 * 7 }} and comes back as 49 is the tell that the engine evaluated it, and the same door serves far more than arithmetic.

The fix is to treat every field from an uploaded file as data you pass into a template, never as the template you evaluate. The full class, including how a rendered field escalates to remote code, lives in our write up on server side template injection. In a data pipeline the danger is easy to miss, because the field looks like a harmless label sitting next to real records.

3. Remote code data loaders

Some dataset formats let the dataset ship its own loader script, and the framework fetches and runs that script as part of “just loading the dataset.” It is sold as convenience: the dataset knows best how to parse itself, so let it. It is also a straight line from upload to execution, because the loader script is code the uploader wrote and your worker obediently runs. A flag that enables remote code on load is a flag that lets any uploaded dataset run on your machine. The feature and the vulnerability are the same feature.

How to stop a loader from running someone else’s code

None of these need a clever payload. They need a loader that trusts the file too much. Tighten that trust and the class mostly closes.

  • Prefer safe formats and safe loaders. Use safetensors for model weights and a safe YAML loader for configuration. A format that cannot encode behaviour cannot be turned into a payload.
  • Never deserialize untrusted pickle. If a file arrived from outside, do not pickle.load it. Convert at the boundary to a format that only carries data, and reject the rest.
  • Disable or sandbox remote code loaders. Turn off any option that fetches and runs a loader script. If you genuinely need one, run it in a throwaway sandbox with no route back to anything that matters.
  • Isolate the processing worker. Give it no standing credentials and lock its egress. If a file does run, it should run in a box that can reach nothing and prove nothing about who it is.
  • Treat every uploaded artifact as hostile. A model file, a dataset, a config, a checkpoint. Assume each one is hostile until proven otherwise, and design the ingestion step as if it will run.

A different kind of pipeline problem

Two nearby ideas are worth keeping separate. Poisoned pipeline execution is about CI and CD, where a change to build config or a pull request runs attacker steps inside your build system. That is a pipeline too, but the untrusted input is a repository change, not an ingested file, and the target is the builder rather than the loader. RAG data poisoning plants content that bends what a model answers later. The contrast is sharp: poisoning retrieval changes an answer, while the bugs here run code on the worker at load time, before any answer exists. More posts on this family sit under injection and input.

The theme across all three mechanisms is one assumption. A system that ingests data treats loading as a passive step, and an attacker turns loading into execution. This is exactly the kind of assumption an autonomous researcher that tests assumptions, rather than matching payloads, is built to probe: it learns what your loader trusts, forms an idea about where that trust is misplaced, and proves it by making a benign looking file do something a file should never be able to do. More on how we think about it sits on our about page.

Frequently asked questions

What is code execution in data pipelines?

It is when a system that ingests data runs an attacker’s code just by loading an uploaded file. A dataset, a model artifact, or a config can carry instructions that run on the processing worker before anyone inspects the file, so for an ingestion system processing untrusted data is the same as executing untrusted code.

Why is loading a pickle file dangerous?

Because a pickle file is not only data, it is a small program that rebuilds an object, and it can carry instructions that run the moment it is deserialized. Loading a pickle based model or dataset from an untrusted source hands the file author a callback into your process. Prefer a safe format such as safetensors for weights, and never deserialize untrusted pickle.

How does template injection reach a data loader?

Loaders often render fields from a dataset config through a template engine to build paths or labels. If a value inside the uploaded data reaches the engine as the template itself, the uploader is writing the template and can run code. Treat every field from an uploaded file as data passed into a template, never as the template you evaluate.

What is a remote code data loader?

It is a dataset format that ships its own loader script, which the framework fetches and runs as part of loading the dataset. That turns a plain upload into code your worker runs. Disable any option that fetches and runs remote loader scripts, or run it in a sandbox with no standing credentials and locked egress.

How do you prevent code execution when ingesting files?

Prefer safe formats and safe loaders, never deserialize untrusted pickle, and disable or sandbox loaders that can fetch and run remote code. Isolate the processing worker so it has no standing credentials and locked egress, and treat every uploaded artifact as hostile until proven otherwise.


Put an autonomous researcher on your own systems

UnboundCompute is an autonomous security researcher that reasons about how an application fits together and proves the access control and injection bugs it finds. We are opening a small number of founding design partner seats: private early access pointed at a staging target you choose, and a say in what it looks for. If your team ships software worth pressure testing, apply to the design partner program.