CVE-2026-25874 affects Hugging Face LeRobot v0.4.3 and earlier, allowing an unauthenticated remote attacker to execute arbitrary commands by sending crafted bytes to an exposed gRPC endpoint. The flaw carries a CVSS score of 9.8 and remained unpatched in the official PyPI release as of late April 2026. If you run a LeRobot PolicyServer on a network-reachable host, treat it as potentially compromised until mitigations are applied.
That is the specific headline. The broader story is more important for most readers: unsafe deserialization using Python’s pickle module is endemic across the ML/AI stack, and LeRobot is one visible consequence of a design pattern that affects PyTorch, scikit-learn, joblib, and dozens of model-serving frameworks running in production today.
What Is CVE-2026-25874
LeRobot is Hugging Face’s open-source platform for real-world robotics research, with more than 24,000 GitHub stars. Its async inference component, PolicyServer, accepts control instructions from remote clients over gRPC and uses a Python-based Policy model to generate robot actions in real time.
The vulnerability lives in lerobot/async_inference/policy_server.py. Two gRPC RPC handlers — SendPolicyInstructions() and SendObservations() — extract raw data bytes from the incoming protobuf message and pass them directly to pickle.loads():
# Vulnerable pattern in lerobot/async_inference/policy_server.py
def SendPolicyInstructions(self, request, context):
payload = pickle.loads(request.data) # arbitrary code executes here
# application-level type checking occurs after this line — too late
...
def SendObservations(self, request, context):
obs = pickle.loads(request.data) # same flaw
...
Python’s pickle module reconstructs objects by executing __reduce__ methods embedded in the serialized stream. Code runs at parse time, before any application-level validation can fire. There is no way to safely filter a pickle payload after calling pickle.loads() — the damage happens during the call itself.
A second critical design flaw compounds the first: the gRPC server starts with add_insecure_port(), which disables TLS. No transport encryption and no authentication mechanism are configured, so any host with network access to the gRPC port can deliver an arbitrary payload to either endpoint without credentials.
Confirmed affected: LeRobot v0.4.3, the current PyPI release as of April 2026. All earlier versions sharing the same policy_server architecture are also vulnerable. No official patched release was available on PyPI as of the Resecurity advisory published April 27, 2026.
How an Attacker Exploits This
Exploitation requires no credentials, no prior foothold, and no user interaction. An attacker who can reach the gRPC port (default: 50051) crafts a serialized pickle payload containing a __reduce__ call that invokes an OS command, then sends it as the data field of a SendPolicyInstructions or SendObservations request.
A minimal proof-of-concept payload generator illustrates the simplicity of the attack:
import pickle, os, grpc
class RCEPayload:
def __reduce__(self):
return (os.system, ("id > /tmp/pwned",))
payload = pickle.dumps(RCEPayload())
# Deliver to the exposed endpoint (requires LeRobot-generated proto stubs)
channel = grpc.insecure_channel("target-host:50051")
stub = PolicyServerStub(channel)
stub.SendPolicyInstructions(PolicySetup(data=payload))
When the server calls pickle.loads(request.data), os.system("id > /tmp/pwned") executes with the privileges of the LeRobot process — typically the user that owns the GPU and model weights. Production robotics deployments often run with elevated permissions or as root to manage hardware interfaces, which makes the effective blast radius a full system takeover.
Beyond data exfiltration, successful exploitation of a robotics controller can manipulate physical actuators, corrupt active policy weights mid-operation, or use the compromised host as a pivot into the connected research or manufacturing network. The CVE advisory explicitly lists disruption of robotic operations as an impact category — this is not a theoretical edge case.
Why This Matters for Security
The CVSS 9.8 score and the project’s popularity guarantee attention, but the structural concern extends well past this specific CVE. The LeRobot pattern — call pickle.loads() on unauthenticated network bytes, skip TLS, skip auth — is not unusual in ML engineering. It is a widely replicated default that practitioners encounter repeatedly across the ecosystem.
Three dimensions of impact are worth separating clearly:
Credential exposure. GPU-backed training and inference hosts routinely hold cloud provider tokens, Hugging Face API keys, dataset access credentials, and SSH keys. A single RCE gives an attacker everything needed to exfiltrate or sell those assets. In the SAP npm supply chain attack from April 2026, attackers pivoted from a backdoored package to GitHub token theft at scale within hours of initial compromise — the same escalation path applies here.
Physical safety. Robotic systems that accept policy instructions from a compromised controller can be directed to execute harmful physical actions. This is not a theoretical concern; the advisory’s impact section explicitly covers it.
Ecosystem amplification. LeRobot is actively deployed across academic robotics labs, corporate R&D environments, and individual builders. A misconfigured deployment that exposes port 50051 to the internet is trivially exploitable by automated scanners. As AI tooling shortens the window between public disclosure and active exploitation, unpatched internet-facing systems get swept up faster than they used to.
Unsafe Deserialization Across the Machine Learning Stack
CVE-2026-25874 is the loudest recent example, but the underlying problem — ML engineers loading data with pickle without treating the operation as code execution — is pervasive across the ecosystem.
PyTorch: torch.load uses pickle by default
PyTorch’s torch.load() deserializes model checkpoints using pickle. Loading a checkpoint from any source you do not fully control — a Hugging Face Hub model card, a third-party dataset, a shared S3 bucket — is functionally equivalent to running untrusted Python code on your machine:
# Unsafe — executes arbitrary code if the checkpoint file is malicious
model = torch.load("weights.pt")
# Partial mitigation — restricts deserialization to a safelist of tensor types
# Not a hardened security boundary in PyTorch < 2.6; known bypasses exist
model = torch.load("weights.pt", weights_only=True)
The weights_only=True flag significantly reduces the attack surface, but it has documented bypasses in versions prior to PyTorch 2.6. Treat it as a useful defense-in-depth measure while migrating to safer formats — not as a complete fix.
scikit-learn and joblib: pickle everywhere, silently
Scikit-learn uses joblib for model serialization, which is itself pickle-based. Every .pkl, .joblib, or .model file exchanged between teams represents a potential code execution vector on the receiving end:
import joblib
# Deserializes using pickle internally — runs arbitrary code if file is malicious
clf = joblib.load("classifier.pkl")
In CI/CD pipelines that cache trained model artifacts between runs, a supply chain compromise of the artifact store translates directly to code execution on build agents — the same environment that typically holds repository secrets, deployment keys, and cloud provider credentials.
MLflow and shared model registries
MLflow’s Python model format serializes custom pyfunc models using pickle. A malicious model artifact pushed to a shared registry executes on any machine that calls mlflow.pyfunc.load_model(), including production inference servers with access to customer data. Shared model registries without artifact integrity verification are effectively trusted-but-unverified code distribution channels.
Malicious models on Hugging Face Hub
Hugging Face runs automated pickle scanning on uploaded model files and flags suspicious __reduce__ patterns. The Hub has discovered and removed hundreds of malicious pickle files since launching this feature. The scanning is heuristic-based and can be evaded with encoding tricks, and downloads that occurred before detection are not retroactively protected. The Hub’s pickle security documentation describes both the scanning mechanism and its known limitations.
Mitigation and Safe Loading Patterns
Fix LeRobot immediately
The community fix is PR #3048 by Chocapikk, which replaces pickle with safetensors and JSON throughout the async inference layer. Until Hugging Face ships a patched release to PyPI, install directly from the fixed commit:
# Install from the fixed commit once PR #3048 is merged
pip install git+https://github.com/huggingface/lerobot.git@<fixed-commit-sha>
Regardless of patch status, harden the gRPC server configuration immediately:
# Remove the insecure binding
# server.add_insecure_port("[::]:50051") ← remove this line
# Replace with TLS and mutual authentication
server_credentials = grpc.ssl_server_credentials([(private_key, cert_chain)])
server.add_secure_port("[::]:50051", server_credentials)
Restrict the gRPC port to localhost or a private network segment. If the PolicyServer must be reachable over a network, add a gRPC interceptor that validates a bearer token on every incoming call before any handler executes.
Use safetensors for model weights
Safetensors is a format developed by Hugging Face that stores raw tensor data and metadata only. It cannot encode arbitrary Python objects and therefore cannot execute code during loading:
from safetensors.torch import load_file, save_file
# Save
save_file(model.state_dict(), "weights.safetensors")
# Load — no pickle, no code execution possible
state_dict = load_file("weights.safetensors")
model.load_state_dict(state_dict)
Safetensors is also faster to load than pickle-based formats: benchmarks show up to 3× speedup for large models due to memory mapping support. Most major model families on the Hugging Face Hub now distribute safetensors variants by default. When both .safetensors and .bin files are available, choose safetensors.
Validate all third-party artifacts
For pipelines that must load pickle-based files from external sources, apply these controls in order:
- Verify file integrity with a cryptographic checksum against a trusted manifest before loading.
- Run artifacts through
picklescanor the Hugging Face Hub scanner before importing into any environment. - Load models in an isolated, network-restricted container or VM where the blast radius of a malicious payload is contained.
- For structured data and configuration payloads — the exact use case in LeRobot’s
SendPolicyInstructions— replace pickle with JSON or native protobuf fields. Neither format can execute code during parsing.
Conclusion
CVE-2026-25874 is exploitable today with fewer than twenty lines of Python against a 24,000-star project, and it carries CVSS 9.8 because the preconditions — no authentication, no TLS, pickle deserialization of network data — could hardly be worse. The LeRobot fix is straightforward: apply PR #3048, switch to add_secure_port(), and enforce authentication on every gRPC call. The fix for the broader ML stack requires a habit change: treat every .pkl, .pt, and .joblib file loaded from outside your organisation as untrusted code execution, migrate model weights to safetensors, and validate structured payloads with a schema rather than pickle.
See our analysis of how AI is accelerating the exploit timeline — AI Industrializes Cybercrime as Mean Time-to-Exploit Hits Negative Seven Days →
For any query contact us at contact@cipherssecurity.com

