Skip to content

CVE-2026-9856: Verified Reproduction

CVE-2026-9856: huggingface/transformers <5.10.0: path traversal via chat template dict keys in save pretrained → arbitrary file write → RCE cron.d drop

CVE-2026-9856 is verified against the affected target. Vulnerability class: RCE. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00321.

REPRO-2026-00321 RCE Aug 23, 2026 CVE entry .txt
Severity
HIGH
Confidence
HIGH
Reproduced in
0m 19s
Tool calls
124
Spend
$0.04
01 · Overview

What Is CVE-2026-9856?

CVE-2026-9856 is a high-severity RCE vulnerability. Pruva has independently reproduced it and publishes a verified, runnable proof-of-concept (reproduction REPRO-2026-00321).

02 · Severity & CVSS

CVE-2026-9856 Severity

CVE-2026-9856 is rated high severity.

HIGH threat level
Weakness CWE-22 — Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

High — serious impact or readily exploitable. Prioritize remediation.

How to Reproduce CVE-2026-9856

$ pruva-verify REPRO-2026-00321
or curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00321/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-9856

Remote code execution — reproduced
  • reached the target end-to-end
  • full exploit chain demonstrated
  • high confidence
  • the upstream fix blocks the same trigger
Trigger

tokenizer_config.json chat_template dict key '../../../etc/cron.d/hf_pwn' and cron command content

Attack chain
  1. AutoTokenizer.from_pretrained(attacker_model).save_pretrained(out)
  2. save_chat_templates()
  3. /etc/cron.d/hf_pwn.jinja
  4. pre-existing cronie reload
  5. crond-spawned /bin/sh
How the agent worked 292 events · 124 tool calls · 51 min
51 minDuration
124Tool calls
67Reasoning steps
292Events
8Dead-ends
Agent activity over 51 min
Policy
2
Support
8
Repro
111
Judge
40
Variant
124
Verify
2
0:0051:22

Root Cause and Exploit Chain for CVE-2026-9856

Versions: >=4.52.0, <5.10.0 (verified vulnerable: 5.9.0;Fixed: 5.10.0 (yanked ~20 min after release) / 5.10.1+.

huggingface/transformers <5.10.0 contains a path traversal (CWE-22) in save_pretrained(). When a tokenizer (or processor) is loaded from an attacker-controlled artifact whose tokenizer_config.json carries a chat_template dictionary, the dictionary keys are used verbatim as filenames by save_chat_templates() (src/transformers/tokenization_utils_base.py) and by ProcessorMixin.save_pretrained() (src/transformers/processing_utils.py): template_filepath = os.path.join(chat_template_dir, f"{template_name}.jinja") with no sanitization. A traversal key such as ../../../etc/cron.d/hf_pwn escapes the save directory and writes fully attacker-controlled content (with a forced .jinja suffix) anywhere the victim process can write. This run demonstrates the full impact chain: the escaped file is dropped into /etc/cron.d, where a pre-existing, genuine cronie daemon naturally loads it and independently spawns /bin/sh running the attacker's command — arbitrary code execution with the victim's privileges.

  • Package: huggingface/transformers (save_pretrained of PreTrainedTokenizerBase and ProcessorMixin).
  • Affected versions: >=4.52.0, <5.10.0 (verified vulnerable: 5.9.0; NVD's <=5.8.0.dev0 understates the range).
  • Fixed versions: 5.10.0 (yanked ~20 min after release) / 5.10.1+.
  • Risk: High. A victim application that loads an attacker-controlled model (from_pretrained) and later calls save_pretrained() — automatic in Trainer checkpointing, model conversion/re-hosting, and fine-tune export — writes attacker-controlled content to arbitrary filesystem paths. Realistic execution targets include /etc/cron.d (cron ignores the .jinja extension) and overwriting existing .jinja templates rendered by a service. CVSS UI:R; the attacker needs no authentication.

Impact Parity

  • Disclosed/claimed maximum impact: arbitrary file write → remote/code execution (e.g., cron.d drop), code_execution.
  • Reproduced impact from this run: code_execution — two fresh, isolated victim processes running real transformers==5.9.0 each produced a unique marker file written by a /bin/sh process that the genuine cronie 1.7.2 daemon independently spawned from the attacker-controlled /etc/cron.d/hf_pwn.jinja. The identical artifact against transformers==5.10.1 raised ValueError, wrote no cron file, and produced no marker.
  • Parity: full.
  • Not demonstrated: nothing — the claimed code-execution impact was reproduced end to end through the real library API and a real system daemon.

Root Cause

save_chat_templates() iterates tokenizer.chat_template.items() when the chat template is a dict and computes template_filepath = os.path.join(chat_template_dir, f"{template_name}.jinja"). template_name comes verbatim from tokenizer_config.json, which is fully attacker-controlled when the model artifact is untrusted. No normalization or containment check is applied, so keys containing .. (or absolute paths) escape OUT/additional_chat_templates/. The library only mkdirs its own additional_chat_templates dir, so the write succeeds whenever the attacker-chosen parent directory (e.g. /etc/cron.d) already exists and is writable by the victim. The identical flaw exists in ProcessorMixin.save_pretrained() (processing_utils.py).

Fix: PR #46191, merge commit eaaaf8494dd5386634ae37d1d122212fdc315be5 (2026-05-25), first shipped in 5.10.0/5.10.1. The fix resolves template_filepath's parent and compares it to the resolved chat_template_dir, raising ValueError on mismatch (a 3-line guard in both files), plus regression tests using a ../../PWNED key.

Reproduction Steps

  1. bundle/repro/reproduction_steps.sh (self-contained; helper: bundle/repro/hf_cron_exploit.py).
  2. The script:
    • Anchors source identity to the fixed commit eaaaf8494dd5386634ae37d1d122212fdc315be5: its parent (47949d3a0e1cf9248f2a3eb3cd0deb12ee37b9e9) lacks the guard, the fix commit contains it (logs/source_identity.log).
    • Builds two Docker images from the immutable fedora:42 digest with cronie 1.7.2 and the exact PyPI releases transformers==5.9.0 (vulnerable) and transformers==5.10.1 (fixed).
    • In each isolated container: starts a real crond before the attacker input, builds a tokenizer whose tokenizer_config.json contains chat_template = {"../../../etc/cron.d/hf_pwn": "* * * * * root /bin/sh -c \"echo '<unique-marker>' > /proof/<unique-marker>.txt\""}, then calls the real API AutoTokenizer.from_pretrained(dir).save_pretrained(out).
    • Waits for cronie to naturally load /etc/cron.d/hf_pwn.jinja and spawn the payload; the script never executes the dropped file.
    • Runs two fresh vulnerable victims and one fixed negative control.
  3. Expected evidence: SAVE_RESULT=RETURNED, CRON_FILE_PRESENT=true, CRON_CONTENT_MATCH=true, cronie's own log_it ... CMD (/bin/sh -c ...) line, and a unique marker file per vulnerable attempt; SAVE_RESULT=BLOCKED / CRON_FILE_PRESENT=false / no marker for the fixed control.

Evidence

  • bundle/logs/reproduction_steps.log — full driver log (two consecutive full passes succeeded; markers ...-1651 then ...-2249).
  • bundle/logs/source_identity.log — fix-commit anchor and guard diff.
  • bundle/logs/vulnerable_cron_attempt1.log, bundle/logs/vulnerable_cron_attempt2.logSAVE_RESULT=RETURNED, CRON_FILE_PRESENT=true, CRON_CONTENT_MATCH=true, MARKER_PRESENT=true, and cronie lines such as log_it: (root 92) CMD (/bin/sh -c "echo 'CVE-2026-9856-RCE-VULNERABLE-1-2249' > /proof/...").
  • bundle/logs/fixed_cron_control.logSAVE_RESULT=BLOCKED, CRON_FILE_PRESENT=false, MARKER_PRESENT=false (ValueError guard).
  • bundle/repro/marker_run1.txt, bundle/repro/marker_run2.txt — unique per-attempt markers written by the crond-spawned shell.
  • bundle/repro/negative_control_fixed.json — strict negative-control observation (target_path_reached=true, marker_present=false).
  • bundle/repro/runtime_manifest.json — entrypoint function_call, target identity, and artifact digests.
  • Environment: Docker 27.5.1, immutable base fedora@sha256:99e203b80b1c3d8f7e161ec10a68fd02b081ef83a3963553e513c82846b97814, cronie 1.7.2, Python 3.13, transformers==5.9.0 / 5.10.1, x86_64.

Recommendations / Next Steps

  • Upgrade to transformers>=5.10.1 (5.10.0 was yanked).
  • The upstream guard (resolve the template path's parent and require it to equal the resolved chat_template_dir) is the correct containment fix; downstream backports should mirror it in both tokenization_utils_base.py and processing_utils.py.
  • Defensive controls: never call save_pretrained() on artifacts loaded from untrusted sources without sandboxing; run converters/exporters with least privilege so /etc/cron.d and similar directories are not writable.
  • Detection: watch for non-system processes opening files under /etc/cron.d, and for processes whose parent is crond executing unexpected commands.

Additional Notes

  • Idempotency: the script was executed twice consecutively in this run; both passes confirmed (exit 0), with fresh unique markers per pass. Images are rebuilt deterministically from the immutable base digest; per-attempt containers are removed after each case.
  • The forced .jinja suffix does not prevent cron execution: cronie loads every file in /etc/cron.d regardless of extension. The write requires the victim to have write permission on the chosen parent directory (root in the demonstration container, matching the ticket's stated precondition).
  • from_pretrained() alone is safe; save_pretrained() is the trigger.
  • This run independently revalidated the durable mechanics from prior exploit-knowledge records (arbitrary write primitive 793c4388-299a-489f-a791-5e7b7f2e66d3, crond-mediated control flow ffcce804-7fa3-4ec4-a772-357db3da686c, command-execution capability 19959132-b2f0-4e9f-8e66-d7e6ab23edf5) with fresh current-run evidence.

CVE-2026-9856 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:001:47
0:00
session startedaccounts/fireworks/models/kimi-k3 · CVE-2026-9856 · REPRO-20
0:12
0:13
web search
0:57
0:57
extract_facts
no facts extracted
0:59
0:59
supportclaim_contract
1:08
1:08
1:08
1:10
1:10
1:13
1:13
1:16
1:16
$cat /pruva/project-cache/.pruva/proof-carry/latest_confirmed/repro/validation_verdict.json 2>/dev/null; echo ---; cat /pruva/project-cache/.pruva/proof-carry/latest_confirmed/proof_carry_manifest.json 2>/dev/null | head -50
0.2s
EXPLOIT-KNOWLEDGE PREFLIGHT REVIEW REQUIRED: `run_shell` is blocked until this agent calls `review_exploit_knowledge_preflight` with snapshot_sha256 `176d395afa8fa9b312276c48ca3a9ac6818850d1ebd813b6d67c7aada052717d` and exactly one disposition for each candidate ID [15244b32-0708-4e08-9f01-09b9e07e1e89, 19959132-b2f0-4e9f-8e66-d7e6ab23edf5, 1aeef5ea-be31-4c14-8b92-ac21a5346810, 5ead8ea5-a615-4078-a071-81a9bdaf5ea8, 6645ee59-6df2-462b-ade4-5f081518865e, 68168c1d-98bf-402e-9749-b65528fb6181, 7234c...
1:16
error

Unknown error

1:19
1:19
$true
0.2s
EXPLOIT-KNOWLEDGE PREFLIGHT REVIEW REQUIRED: `run_shell` is blocked until this agent calls `review_exploit_knowledge_preflight` with snapshot_sha256 `176d395afa8fa9b312276c48ca3a9ac6818850d1ebd813b6d67c7aada052717d` and exactly one disposition for each candidate ID [15244b32-0708-4e08-9f01-09b9e07e1e89, 19959132-b2f0-4e9f-8e66-d7e6ab23edf5, 1aeef5ea-be31-4c14-8b92-ac21a5346810, 5ead8ea5-a615-4078-a071-81a9bdaf5ea8, 6645ee59-6df2-462b-ade4-5f081518865e, 68168c1d-98bf-402e-9749-b65528fb6181, 7234c...
1:19
error

Unknown error

1:25
1:30
1:31
1:31
error

Unknown error

1:33
1:47
1:47
08 · How to Fix

How to Fix CVE-2026-9856

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-9856 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-9856

Is CVE-2026-9856 exploitable?

Yes. Pruva independently reproduced CVE-2026-9856 and verified the exploit fires end-to-end in a sandboxed environment. A runnable proof-of-concept script and the full agent transcript are on this page (reproduction REPRO-2026-00321).

How severe is CVE-2026-9856?

CVE-2026-9856 is rated high severity.

What type of vulnerability is CVE-2026-9856?

CVE-2026-9856 is classified as CWE-22 (Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')), a RCE vulnerability.

How can I reproduce CVE-2026-9856?

Pruva provides a verified reproduction script on this page. Download it and run it inside an isolated environment such as a container or virtual machine — never against production. The reproduction was confirmed end-to-end by Pruva's automated agents.

Is the CVE-2026-9856 reproduction verified?

Yes. Pruva reproduced CVE-2026-9856 with high confidence in a sandboxed environment, capturing the full agent transcript and artifacts as evidence.
11 · References

References for CVE-2026-9856

Authoritative sources for CVE-2026-9856 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.