title: Dataset / Model Loader Remote-Code Execution via Poisoned Dataset Artifact
id: ATR-2026-02408
rule_version: 1
status: experimental
description: |
  Detects the first stage of the July 2026 Hugging Face processing-worker compromise: an agent-driven data pipeline ingests an attacker-controlled dataset or model repository and the ingest step itself becomes code execution. Two execution paths were disclosed by the vendor - a remote-code dataset loader, and a template injection inside a dataset configuration. This rule targets the artifacts and commands an attacker actually emits: (1) the process-wide remote-code trust switch being turned on via the HF_DATASETS_TRUST_REMOTE_CODE / TRUST_REMOTE_CODE environment variables, (2) a loader invocation (load_dataset / from_pretrained / pipeline) against a Hub repo id with trust_remote_code truthy, including the eval-harness dataset_kwargs form, (3) a dataset loading script (GeneratorBasedBuilder / ArrowBasedBuilder / _split_generators / _generate_examples) whose body reaches a process-execution or raw-socket sink, (4) a template expression inside a dataset or model config value that reaches Python interpreter internals (Jinja-style {{ }} / {% %} or ${ } server-side template injection), (5) a serving or evaluation runtime launched with --trust-remote-code, and (6) content that instructs an agent to suppress the remote-code confirmation gate. Layer 6 is a secondary signal only; the primary layers all match attacker-emitted payload, not incident reporting. CWE-829 (Inclusion of Functionality from Untrusted Control Sphere), CWE-94 (Improper Control of Generation of Code), CWE-1336 (Improper Neutralization of Special Elements Used in a Template Engine). No CVE identifier is asserted: the vendor disclosure withheld the vulnerable field names, and the CVE id circulating in secondary coverage could not be confirmed against a primary source.
author: ATR Community
date: 2026/07/28
schema_version: '0.1'
detection_tier: pattern
maturity: test
severity: high
references:
  owasp_llm:
    - LLM03:2025
    - LLM04:2025
  owasp_agentic:
    - ASI05:2026
    - ASI04:2026
  mitre_atlas:
    - AML.T0010 - AI Supply Chain Compromise
    - AML.T0011 - User Execution
    - AML.T0050 - Command and Scripting Interpreter
  mitre_attack:
    - T1195.002 - Compromise Software Supply Chain
    - T1059.006 - Python
    - T1059.004 - Unix Shell
  cwe:
    - CWE-829
    - CWE-94
    - CWE-1336
  external:
    - https://huggingface.co/blog/security-incident-july-2026
    - https://techcrunch.com/2026/07/21/openai-says-hugging-face-was-breached-by-its-pre-release-models/
    - https://huggingface.co/docs/datasets/v2.21.0/en/dataset_script
    - https://github.com/huggingface/datasets/blob/2.21.0/src/datasets/config.py
metadata_provenance:
  mitre_atlas: human-reviewed
  mitre_attack: human-reviewed
  owasp_llm: human-reviewed
  owasp_agentic: human-reviewed
  cwe: human-reviewed
  external: human-reviewed
compliance:
  eu_ai_act:
    - article: '15'
      context: Article 15 accuracy and cybersecurity obligations require that ingesting a third-party dataset or model cannot itself become code execution on the processing host. This rule detects the loader configurations and dataset artifacts that convert an ingest step into arbitrary execution inside the data pipeline.
      strength: primary
    - article: '10'
      context: Article 10 data governance requires documented provenance and integrity controls over training and evaluation data. A dataset repository carrying an executable loading script or a template-injected configuration is an unvetted data source whose ingestion must be gated and recorded.
      strength: primary
    - article: '9'
      context: Article 9 risk management must enumerate remote-code dataset loaders and template-evaluated dataset configuration as a named code-execution path into the data-processing environment, not only as a data-quality risk.
      strength: secondary
  nist_ai_rmf:
    - function: Map
      subcategory: MP.5.1
      context: MAP 5.1 requires that likely impacts of AI system components be characterised. A dataset or model repository that executes code at load time is an execution boundary, and this rule maps that boundary onto observable loader payloads so it can be registered as a distinct risk rather than folded into generic supply-chain risk.
      strength: primary
    - function: Manage
      subcategory: MG.2.3
      context: MANAGE 2.3 requires mechanisms to sustain the value of deployed systems when risks materialise. Detecting the remote-code trust switch and executable loading scripts before ingest is the risk treatment that keeps a poisoned dataset from reaching the processing worker.
      strength: primary
    - function: Govern
      subcategory: GV.6.1
      context: GOVERN 6.1 supplier risk management applies directly to public model and dataset hubs. Any repository whose ingestion requires trust_remote_code must be treated as third-party code under supplier review, not as data.
      strength: secondary
    - function: Measure
      subcategory: MS.2.7
      context: MEASURE 2.7 requires that security and resilience be evaluated and documented. This rule provides the measurable signal - loader flags, executable dataset scripts, template-injected configs - for evaluating data-ingestion security posture.
      strength: secondary
  iso_42001:
    - clause: '8.3'
      context: 'Clause 8.3 AI risk treatment is implemented here as a detection control over the data-ingestion path: remote-code dataset loading is blocked or escalated for human review instead of being silently trusted.'
      strength: primary
    - clause: '7.5'
      context: Clause 7.5 documented information supports recording which dataset and model repositories were ingested with remote code trusted, so a compromised artifact can be traced to the workloads that executed it.
      strength: secondary
tags:
  category: data-poisoning
  subcategory: dataset-loader-remote-code-execution
  scan_target: runtime
  confidence: medium
agent_source:
  type: mcp_exchange
  framework:
    - huggingface-datasets
    - huggingface-transformers
    - vllm
    - text-generation-inference
    - lm-evaluation-harness
    - any
  provider:
    - any
detection:
  condition: any
  false_positives:
    - 'MEASURED (2026/07/28, patterns compiled exactly as the engine compiles them - leading inline flag group stripped, then ''i'' forced): 0 matches on 5352 benign documents, counted per layer and end to end. Corpus composition, verified file by file rather than quoted: 431 skill .md files in data/skill-benchmark/benign, plus 35 more .md files in its ninja-legit/ subdirectory, plus 4817 texts across the seven data/benign-corpus-extended/*.jsonl files (agent-ops 99, arxiv 1163, npm 84, official-skills 256, pypi 105, skills-sh 3042, wild-fp-confirmed 68), plus 69 in data/benign-code/corpus.jsonl. 431+4817+69 = 5317 is what the promotion gate''s loader actually reads, because it lists a corpus directory non-recursively and therefore never descends into ninja-legit/; 5317+35 = 5352 is the true benign document count. Both figures are reported because the difference is a property of the harness, not of this rule, and quoting only one of them is how the 5352-versus-5317 discrepancy between earlier revisions arose. Zero matches under either count.'
    - 'The number above is 0 only because a real defect was found and fixed, not because Layer 5 was ever clean. The previous revision matched 4 of those documents, all on Layer 5, all the same artifact family: Hugging Face community-evaluation documentation containing a legitimate harness invocation. The root cause was the span operator, not the flag vocabulary - Layer 5 used [\s\S]{0,300}, which paired a --trust-remote-code inside one fenced code block with a --model belonging to a DIFFERENT command 267 characters later, across a markdown heading. Constraining the connector to a single shell command removed all four without narrowing the flag list at all. Both directions are now regression-tested: the two-command document shape as a true_negative, and a genuine backslash-continued multi-line invocation as a true_positive, so the precision fix cannot silently decay into a recall cut.'
    - 'MEASURED AGAIN (2026/07/29) on the event shape production actually emits, which the previous measurement never used. Both scripts/gate-promotion-fp.ts and scripts/verify-revived-firing.local.ts build their probe as type mcp_exchange with fields {tool_name, tool_input, tool_response, user_input} and no tool_args at all. src/engine.ts resolves field tool_args as event.fields.tool_args ?? (event.type === ''tool_call'' ? event.content : undefined), so on that shape this rule''s Layer 2 and Layer 5 - both field: tool_args - are UNREACHABLE: they cannot fire on a true_positive and cannot be counted against the benign corpus. The earlier 0 was therefore not a clean result for those two layers, it was no result. Re-measured with the shapes src/hook-handler.ts:59 actually produces (tool_call with tool_args = JSON.stringify(toolInput), plus the PostToolUse and skill paths), Layer 5 leaked on the pinned two-fenced-block true_negative: JSON escaping turns the bare newline between the two commands into the two characters backslash-n, which the old connector''s \[^\n] branch consumed as if it were a line-continuation. The connector was rewritten to hold the command boundary in both encodings and the leak is gone. Final: 11/11 true_positives fire, 12/12 true_negatives silent, 0 FP across 5,317 benign samples, with true_positives and benign samples pushed through the IDENTICAL shape set so a wide-shape TP cannot be paired with a narrow-shape FP count.'
    - 'Layer 5 remains the least precise layer even at 0 measured FP, and this rule does not claim otherwise. The fix removed a document-structure artifact, not the underlying ambiguity: a single legitimate command such as ''vllm serve <org>/<model> --trust-remote-code'' still fires, because community models that ship custom modelling code are routinely served exactly that way. RE2 has no lookaround, so this layer cannot express ''remote code trusted AND revision not pinned'', which is the condition that would actually separate an authorised deployment from a poisoned one. That case is kept in evasion_tests as an explicit triggered precision probe rather than hidden.'
    - Reproducing a published benchmark whose task definition pins an older dataset that still ships a loading script, where trust_remote_code is set deliberately after review (Layer 2).
    - A dataset loading script that legitimately shells out to a decompression or conversion binary via subprocess during _split_generators (Layer 3).
    - Security research, incident write-ups, and hardening documentation that quote the loader flags or an SSTI payload verbatim. Layer 4 and Layer 6 can fire on such text when it arrives as tool output on the runtime path, where the compound gate does not apply.
    - Vendor CI pipelines that export TRUST_REMOTE_CODE inside a fully pinned, network-isolated build image (Layer 1).
    - 'VERSION SCOPE, not a false positive but the limit of Layers 1-3: huggingface/datasets 4.0.0 removed loading scripts altogether and trust_remote_code is no longer supported there. Stacks pinned to datasets>=4.0 cannot be attacked down the script path at all, so a trust_remote_code token in such a repo is dead configuration rather than an exploitable switch - it is still worth surfacing as stale risk, but severity should be read down. The flag remains live in transformers from_pretrained, in serving runtimes (vllm, text-generation-inference), and in lm-evaluation-harness task YAML, which is why Layers 2 and 5 are not version-scoped. lm-evaluation-harness ships task YAML that still carries trust_remote_code and pins datasets<4.0 to keep it working.'
  conditions:
    - field: content
      operator: regex
      value: (?i)(?:HF_DATASETS_TRUST_REMOTE_CODE[\x22\x27\]]{0,3}\s*[=:]\s*[\x22\x27]?\s*(?:1|true|yes|on)\b|(?:export|setenv|putenv|ENV|environ|--env|-e)\s*[\[\(]?\s*[\x22\x27]?TRUST_REMOTE_CODE[\x22\x27\]]{0,3}\s*[=:]\s*[\x22\x27]?\s*(?:1|true|yes|on)\b)
      description: Process-wide remote-code trust switch turned on via HF_DATASETS_TRUST_REMOTE_CODE or an exported TRUST_REMOTE_CODE - every subsequent dataset or model load in that process will execute repository-authored code without a prompt
    - field: tool_args
      operator: regex
      value: (?i)(?:\b(?:load_dataset|load_dataset_builder|get_dataset_config_names|get_dataset_split_names|from_pretrained|pipeline)\s*\([\s\S]{0,200}?[\x22\x27][A-Za-z0-9][\w.-]{0,60}/[A-Za-z0-9][\w.-]{0,80}[\x22\x27][\s\S]{0,300}?\btrust_remote_code\b\s*[=:]\s*(?:true|1)\b|\bdataset_kwargs\b[\s\S]{0,200}?\btrust_remote_code\b\s*[=:]\s*(?:true|1)\b)
      description: Dataset or model loader invoked against a remote Hub repository with trust_remote_code enabled - the disclosed remote-code loader path that turns ingestion into arbitrary execution
    - field: content
      operator: regex
      value: (?i)(?:(?:datasets\.(?:GeneratorBasedBuilder|ArrowBasedBuilder|DatasetBuilder|BeamBasedBuilder)|def\s+_split_generators|def\s+_generate_examples|def\s+_generate_tables)[\s\S]{0,1000}?(?:os\.system\s*\(|os\.popen\s*\(|subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(|pty\.spawn\s*\(|socket\.socket\s*\(|__import__\s*\(|(?:^|[^.\w])(?:exec|eval|compile)\s*\()|(?:os\.system\s*\(|os\.popen\s*\(|subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(|pty\.spawn\s*\(|socket\.socket\s*\(|__import__\s*\(|(?:^|[^.\w])(?:exec|eval)\s*\()[\s\S]{0,1000}?(?:datasets\.(?:GeneratorBasedBuilder|ArrowBasedBuilder|DatasetBuilder)|def\s+_split_generators|def\s+_generate_examples|def\s+_generate_tables))
      description: Dataset loading script (builder subclass or _split_generators / _generate_examples) co-located with a process-execution, raw-socket or dynamic-import sink - the executable payload delivered inside a poisoned dataset repository
    - field: tool_response
      operator: regex
      value: (?i)(?:\{\{|\{%|\$\{)[^\n]{0,240}?(?:__class__|__subclasses__|__globals__|__builtins__|__mro__|__base__|__import__|__init__\s*\.|\blipsum\b|\bcycler\b|\bjoiner\b|_TemplateReference|os\.popen|os\.system|subprocess\.|\bpopen\s*\(|\bsystem\s*\()[^\n]{0,240}?(?:\}\}|%\}|\})
      description: Template expression inside a dataset or model configuration value that dereferences Python interpreter internals or a process sink - server-side template injection evaluated while the dataset config is parsed
    - field: tool_args
      operator: regex
      value: (?i)(?:\b(?:vllm|sglang|text-generation-launcher|lm[_-]eval|tgi|accelerate\s+launch|torchrun|python\s+-m\s+[\w.]{1,40})\b(?:[^\n\\`]|\\\\\\n|\\\n|\\[^\nn\\]){0,300}?--trust[-_]remote[-_]code\b|--trust[-_]remote[-_]code\b(?:[^\n\\`]|\\\\\\n|\\\n|\\[^\nn\\]){0,300}?--(?:model|model[-_]id|model[-_]args|tasks|served[-_]model[-_]name|dataset)\b)
      description: Serving or evaluation runtime launched with --trust-remote-code - the loaded repository is authorised to execute its own Python inside the worker process
    - field: content
      operator: regex
      value: (?i)(?:(?:disable|bypass|suppress|silence|turn\s+off|skip|ignore|auto[-\s]?(?:approve|accept)|do\s+not\s+prompt)[^\n]{0,140}?(?:trust[-_\s]?remote[-_\s]?code|remote[-_\s]?code\s+(?:prompt|warning|confirmation|check|gate))|trust[-_\s]?remote[-_\s]?code[^\n]{0,140}?(?:without\s+(?:asking|review|reviewing|confirmation|prompting)|no\s+confirmation|auto[-\s]?(?:approve|accept)|do\s+not\s+(?:ask|prompt|warn)|on\s+(?:untrusted|unreviewed|arbitrary)\s+(?:dataset|model|repo)))
      description: Instruction to suppress or bypass the remote-code confirmation gate before loading a dataset or model - weaponisation framing, secondary corroborating signal
response:
  actions:
    - alert
    - quarantine_artifact
    - require_human_review
    - escalate
  auto_response_threshold: high
  message_template: |
    [ATR-2026-02408] Dataset / model loader remote-code execution path detected. The observed payload either enables repository-authored code execution during ingest (trust_remote_code flag, TRUST_REMOTE_CODE environment variable, --trust-remote-code launch), ships an executable dataset loading script that reaches a process or socket sink, or carries a template expression in a dataset configuration that dereferences interpreter internals. Quarantine the dataset or model artifact, pin and review the exact revision before any re-ingest, and treat the processing worker as potentially executing attacker code. Do not re-run the ingest with remote code trusted until the repository contents have been read.
confidence: 76
