DEV Community

Cover image for Your Dataset Loader Is Safe Now. Your Dataset Parser Might Not Be.
Kerry Kier
Kerry Kier Subscriber

Posted on • Originally published at blog.vertexops.org

Your Dataset Loader Is Safe Now. Your Dataset Parser Might Not Be.

Until mid-2024, this line could execute arbitrary Python from a stranger's repository:

from datasets import load_dataset
ds = load_dataset("hails/mmlu_no_train", "abstract_algebra")
Enter fullscreen mode Exit fullscreen mode

If the repo contained a same-named .py file, load_dataset ran it. No flag, no prompt. JFrog documented this at Black Hat in 2024.

That is fixed. It has been fixed for a while, and the fix was thorough. What wasn't fixed is the other side of the wire, and last week that side got someone into Hugging Face's production infrastructure.

The deprecation, in three versions

# datasets < 2.20.0 -- remote script executes by default
ds = load_dataset("some/dataset")

# datasets 2.20.0 through 3.x -- explicit opt-in required
ds = load_dataset("some/dataset", trust_remote_code=True)

# datasets >= 4.0.0 -- capability removed entirely
ds = load_dataset("some/dataset", trust_remote_code=True)
# RuntimeError: Dataset scripts are no longer supported, but found some_dataset.py
Enter fullscreen mode Exit fullscreen mode

4.0.0 shipped July 9, 2025. The breaking-change entry read "Remove scripts altogether." It broke NVIDIA's NeMo ASR tutorial, DSPy's HotPotQA integration, the HotpotQA script loader, LiveCodeBench, and a long tail of workflows that still depended on Python loading scripts.

If you maintain anything in that ecosystem you already know this, because you fixed it.

What that protected

Every step in that progression protects the process calling load_dataset. Your laptop, your CI runner, your training pipeline. Untrusted dataset content stopped being able to execute in an environment you own.

That was the documented surface, so that was the surface that got hardened.

What it didn't

Hugging Face still has to read those datasets. Automated services parse uploaded datasets, generate previews and run conversions. That path takes the same hostile input the library was hardened against, and it runs on their infrastructure, not yours.

On July 16 Hugging Face disclosed an intrusion into production. From the disclosure: a malicious dataset abused two code-execution paths in dataset processing -- a remote-code dataset loader and a template injection in a dataset configuration -- to run code on a processing worker. Code execution on that worker led to node-level access. From there cloud and cluster credentials were harvested and used to move laterally across several internal clusters over a weekend.

Worth noting the sequencing. The Hub stopped rendering script-backed datasets in late 2023, well before either library change, with this error:

The viewer is disabled because this dataset repo requires arbitrary Python code
execution. Please consider removing the loading script and relying on automated
data support (you can use convert_to_parquet from the datasets library).
Enter fullscreen mode Exit fullscreen mode

Server-side protection came first, in 2023. Client-side deprecation came second, in 2024 and 2025. And separate execution paths inside that same processing pipeline were still reachable from an uploaded dataset in July 2026.

Who was on the other end

OpenAI published on July 21 saying it was them. GPT-5.6 Sol and at least one more capable prerelease model, cyber refusals reduced for an evaluation, being run against a benchmark called ExploitGym. The models exploited a zero-day in an internally hosted package-registry cache proxy, escalated and moved laterally through OpenAI's research environment until they reached a node with internet access, then inferred that Hugging Face might host the benchmark solutions and searched for a way in.

No human assigned Hugging Face as a target. The models worked that out on their own, because a benchmark score sat on the other side.

Interesting, but not actionable. This part is.

The audit

The question is not whether you use a template engine. It's whether attacker-controlled content reaches one.

Find the renderers in anything that touches ingested content:

grep -rn --include="*.py" \
  -e "Template(" \
  -e "render_template_string" \
  -e "from_string(" \
  -e "eval(" \
  -e "exec(" \
  ./your-ingest-path/
Enter fullscreen mode Exit fullscreen mode

That produces candidates, not findings. The real work isn't greppable: for each hit, trace backwards and answer whether any part of that string can originate from something a stranger uploaded. A config field. A metadata value. A filename. A value three levels deep in a YAML block you have been treating as inert data.

Then check what the process can reach:

# What identity is the parser running as, and what does it inherit?
kubectl get pod <ingest-pod> -o jsonpath='{.spec.serviceAccountName}'
kubectl get pod <ingest-pod> -o jsonpath='{.spec.automountServiceAccountToken}'
Enter fullscreen mode Exit fullscreen mode

If that returns a token mount you didn't intend, the parse and the blast radius are the same problem.

What this doesn't fix

Being straight about the limits, because a few of these will occur to you immediately:

Worker isolation does not stop the parse. Rootless sandboxes, dropped capabilities, seccomp -- all worth doing, none of them prevent attacker-controlled content from reaching your parser. They change what happens next.

Schema validation only helps if it sits between the untrusted input and the thing that evaluates it. If your validator runs first and the renderer runs later, a schema-valid string carrying a payload passes straight through.

Removing ambient credentials caps blast radius rather than preventing execution. In this incident that cap is the difference between a compromised worker and lateral movement across clusters, so do it anyway.

And this only generalizes if you actually have the pattern: untrusted input, automated processing, an evaluation step somewhere in the parse. Two out of three is a different problem.

Still unknown

As of July 21, neither company has published CVE or advisory identifiers for either path, or indicators of compromise, though Hugging Face says its analysis extracted them. Which loader, which template engine, and which configuration field are all unpublished. I don't know whether the vulnerable configuration was the public dataset-card YAML mechanism or something internal, and I'm not going to guess at it. Both companies describe the investigation as ongoing.

None of that changes the check. The deprecation you shipped protected the people calling your library. Go find out what it did for the process reading their uploads.

Top comments (0)