How a robotics AI framework's own serialization design left every connected robot open to remote takeover
The developers knew the code was dangerous. Their own static analysis tool flagged it. They responded by adding a comment to silence the warning, and shipped it anyway.
That comment, # nosec, sits directly next to the line of code at the center of CVE-2026-25874: a call to Python's pickle.loads() inside Hugging Face's LeRobot framework, a widely used open-source toolkit for building AI-powered robots. Any attacker who can reach the service port over a network can send a single crafted message and receive full operating system control of the host, no credentials required, no prior access needed. The exploit was confirmed against the official package downloaded directly from PyPI.
The detail that should alarm anyone running AI infrastructure: the same organization that created the safetensors format specifically because pickle is dangerous for machine learning data shipped its own robotics framework using pickle to deserialize commands from the network, with warnings suppressed. Security knowledge and security practice were completely disconnected, inside the same company.
Narrative · 6 min read
The Context
Hugging Face is the dominant platform for sharing and deploying open-source AI models, often described as the GitHub of machine learning. LeRobot is its open-source robotics framework, designed to let researchers and engineers build AI-powered robots using pre-trained policy models. The framework has accumulated between 21,500 and 24,000 GitHub stars, reflecting broad adoption across academic labs, university robotics programs, and production environments.
The specific component at issue is LeRobot's asynchronous inference module, added in September 2025. It offloads computationally expensive AI policy calculations to a dedicated server called the PolicyServer, which communicates with robot clients over gRPC (a high-performance network protocol common within data center or lab networks). The design is sensible for performance. The security implementation was not.
The Attack, Phase by Phase
Phase 1: The Insecure Architecture
When the async inference module shipped in September 2025, no security review was performed on its network design. The PolicyServer was initialized using add_insecure_port(), meaning it operates with no TLS encryption and no authentication. Any host that can reach the port can connect.
The deeper problem is what happens after connection. Both the PolicyServer and its robot clients use Python's pickle.loads() to deserialize all data exchanged over the channel. The design treats every network peer as implicitly trusted—which is only safe if the network itself is perfectly controlled. In practice, it is not.
Developers were aware the design was flagged as dangerous. Their own static analysis linter identified the pickle.loads() calls as security risks. Rather than replacing the unsafe pattern, developers added # nosec comments directly adjacent to the calls, instructing the linter to ignore them. The warnings were silenced. The code shipped.
Phase 2: The Vulnerability Window
In December 2025, a researcher using the handle "chenpinji" privately reported the issue to Hugging Face through GitHub's Security tab. No response was received. On January 7, 2026, a LeRobot maintainer publicly acknowledged the risk in a GitHub issue, writing that the codebase "needs to be almost entirely refactored." No fix was issued. The last maintainer interaction on the thread was January 12, 2026.
On February 11, 2026, security researcher Valentin Lobstein independently rediscovered the vulnerability and confirmed it with a working proof-of-concept against the official LeRobot v0.4.3 package installed directly from PyPI. A CVE was reserved on February 6 and published on April 23, 2026 with a critical severity score of 9.3 out of 10. As of late April 2026, no patch exists. A fix is planned for version 0.6.0, with no confirmed release date.
Phase 3: How the Exploit Works
An attacker with network access to the PolicyServer port crafts a malicious Python pickle object. Using Python's __reduce__() hook, they embed an arbitrary operating system command inside the object and send it to any of three vulnerable endpoints on the server.
The critical detail is the order of operations: pickle.loads() is called before any type validation or safety check runs. The embedded command executes the moment the server processes the message—before the server has any opportunity to inspect or reject the payload. No credentials, no prior access, and no complex attack chain are required.
The attack is also bidirectional. The GetActions endpoint, which the server uses to push AI policy decisions back to robot clients, also deserializes data using pickle.loads(). A compromised server can push malicious payloads to every connected robot client, turning a single server compromise into a fleet-wide takeover.
Phase 4: Post-Exploitation Impact
AI inference servers typically run with elevated system privileges to manage GPU resources and large datasets. A successful exploit yields full operating system control of that host. From there, an attacker can move laterally to every connected robot client, steal Hugging Face API keys and proprietary model files, corrupt the AI policy models governing robot behavior, and—in deployments where the policy server controls physical hardware—potentially cause unsafe physical operations.
The absence of authentication or encryption on the gRPC channel also means an attacker can maintain persistent access by continuing to send commands through the same exposed port, with no need to install additional malware.
What Made This Possible
-
Authentication was never built in. The gRPC server was initialized with
add_insecure_port()from the start. The entire trust model depended on network isolation. The moment a deployment bound the service to a non-localhost address, that model collapsed. -
An unsafe serialization format was used for network data. Python's
picklewas designed for local object storage, not for deserializing data arriving from the network. Hugging Face itself created thesafetensorsformat to replacepicklein ML pipelines precisely because of this danger—then shippedpickleover an unauthenticated network channel in its own robotics framework. -
Security signals were actively suppressed. The
# noseccomments are not an oversight. They represent a deliberate choice to silence a working security tool rather than address what it found. The signal existed. The tooling worked. The decision was made to ignore it.
Research prototyping culture and production deployment culture are not the same thing, and the gap between them is where this vulnerability lived for months.
What Should Have Stopped This
- Replace
picklewith a safe serialization format. The planned fix for v0.6.0 replacespicklewithsafetensorsfor tensor data and JSON for structured messages. Neither format executes code on deserialization. This is the root cause fix. - Require authentication on the gRPC channel. Switching from
add_insecure_port()toadd_secure_port()with mutual TLS means an attacker who reaches the port cannot send commands without a valid credential. - Run the service as a non-privileged user inside a container. If the PolicyServer process has no elevated privileges, a successful exploit yields a low-privilege shell rather than full OS control.
- Treat
# noseccomments as a mandatory review trigger. Any suppression of a security linter warning should require documented justification and a second reviewer. A comment that silences a warning without a corresponding fix is a deferred vulnerability, not a resolved one.
The Takeaway
This vulnerability is not primarily a story about a single unsafe function call. It is a story about a research tool adopted in production before its security posture was ever examined—and about an organization that publicly solved this exact class of problem for the broader ML community while leaving it unaddressed in its own codebase.
This is the same class of failure as the Stryker Intune wipe and the Axios supply chain attack: a system trusted by design, where the trust was never validated. The meta-pattern across all three: systems fail when they trust a boundary the attacker controls.
Pattern to remember: When a network service deserializes data before authenticating the sender, the authentication step is irrelevant because the payload executes first.
What changed: A # nosec comment in a codebase is now a meaningful audit signal, not a resolved finding. It proves the warning fired, a human saw it, and the decision was made to ship anyway. Detection without remediation is a documented liability.
Technical Deep Dive · 3 min
The Technical Mechanism
CVE-2026-25874 is classified as CWE-502 (Deserialization of Untrusted Data). The vulnerability exists in LeRobot's async inference pipeline, specifically in policy_server.py and robot_client.py, introduced in September 2025.
The gRPC server is initialized via add_insecure_port(), providing no TLS and no authentication. Protobuf message definitions use raw bytes fields to transport Python objects serialized with pickle. Three RPC handlers are vulnerable:
- SendPolicyInstructions (unary RPC): The server calls
pickle.loads(request.data)on thePolicySetup.datafield before any type validation. An attacker sends a craftedPolicySetupmessage with a malicious pickle payload asdata. - SendObservations (streaming RPC): The server reassembles chunked
Observation.databytes across the stream and callspickle.loads()on the reassembled result. The chunking mechanism does not affect exploitability. - GetActions (bidirectional RPC): The robot client calls
pickle.loads(actions_chunk.data)onActions.datareceived from the server. A compromised server can push malicious payloads to all connected clients, making the attack bidirectional.
In all three cases, deserialization occurs before any isinstance() check or other guard. An attacker constructs a malicious class implementing __reduce__() to return a tuple of (os.system, ("command",)) or equivalent. When pickle.loads() processes the payload, Python's deserialization engine calls os.system("command") automatically. The CVSS 4.0 vector AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H confirms: network-accessible, low complexity, no privileges required, no user interaction, full confidentiality/integrity/availability impact.
The # nosec comments adjacent to the pickle.loads() calls confirm that Bandit (Python's standard static analysis security linter) flagged these calls and developers chose suppression over remediation. A separate prior vulnerability, CVE-2025-10772, covers missing authentication in lekiwi_remote.py (ZeroMQ transport), confirming a systemic pattern of authentication gaps across LeRobot's network-facing components.
CVE and Advisories
CVE-2026-25874: Hugging Face LeRobot unauthenticated RCE via pickle deserialization in gRPC PolicyServer. CVSS 4.0 score: 9.3 (Critical). CWE-502. Reserved February 6, 2026; published April 23, 2026. Assigned by VulnCheck.GHSA-f7vj-73pm-m822: GitHub Security Advisory confirming all three vulnerable RPC paths. No fixed version available; Dependabot alerts not supported due to absence of a patched release in a supported ecosystem.- VulnCheck Advisory: Full technical advisory from the CVE assigning organization.
- GitHub Issue #3047: Public disclosure issue with proposed fix in pull request #3048.
MITRE ATT&CK Mapping
| Technique ID | ATT&CK name | How it appeared |
|---|---|---|
| T1190 | Exploit Public-Facing Application | The gRPC PolicyServer is a network-accessible service. Attackers exploit the unauthenticated pickle deserialization endpoint to gain initial access without credentials. |
| T1059.006 | Command and Scripting Interpreter: Python | The exploit abuses Python's pickle deserialization engine and __reduce__() hook to execute arbitrary Python and OS-level commands on the target host. |
| T1552.001 | Unsecured Credentials: Credentials in Files | Post-exploitation access to the host enables theft of Hugging Face API keys and other credentials stored in environment variables or configuration files on the inference server. |
| T1565.001 | Data Manipulation: Stored Data Manipulation | An attacker with OS-level access can corrupt or poison AI policy model files stored on the host, altering robot behavior in subsequent operations. |
| T1021 | Remote Services | The bidirectional GetActions RPC path enables lateral movement from a compromised PolicyServer to all connected robot clients, which also call pickle.loads() on server responses. |
Indicators of Compromise
No network-level indicators of compromise (IOCs) have been published as of April 29, 2026. The attack leaves no distinctive network signature because it uses the legitimate gRPC protocol on the service's standard port with valid protobuf message structure. The malicious content is entirely within the serialized bytes field of the protobuf message, which is opaque to standard network inspection.
Host-level detection is feasible: unexpected child processes spawned by the PolicyServer process, anomalous file writes in /tmp/ or other world-writable directories, and unexpected outbound network connections from the inference server process are all indicators consistent with post-exploitation activity. Reviewing Python process trees for os.system(), subprocess, or shell invocations originating from the gRPC server process is the most direct detection path.
The presence of # nosec comments in policy_server.py adjacent to pickle.loads() calls is a code-level indicator that the unsafe pattern was knowingly retained.
Attribution
No threat actor attribution. This is a vulnerability disclosure. The vulnerability was independently discovered by researcher "chenpinji" (December 2025, private report via GitHub Security tab) and Valentin Lobstein (alias: Chocapikk) of VulnCheck (February 11, 2026, public PoC and CVE assignment). No exploitation in the wild has been reported as of April 29, 2026. Team Cymru threat intelligence advisor Eli Woodward noted that AI infrastructure services of this type are "attractive entry points for both financially motivated actors and more advanced threat groups" due to their privileged access to internal resources.
Primary Sources
- 01.LeRobot Unsafe Deserialization Remote Code Execution via gRPC
VulnCheck · April 23, 2026
- 02.CVE-2026-25874: HuggingFace LeRobot Unauthenticated RCE via Pickle Deserialization in gRPC PolicyServer
Chocapikk (Valentin Lobstein, VulnCheck researcher) · April 22, 2026
- 03.Security: Unsafe pickle deserialization in async inference enables Remote Code Execution (CWE-502)
GitHub (huggingface/lerobot Issue #3047) · February 27, 2026
- 04.LeRobot contains an unsafe deserialization vulnerability (GHSA-f7vj-73pm-m822)
GitHub Advisory Database · April 23, 2026
- 05.CVE-2026-25874: Hugging Face LeRobot Unauthenticated RCE via Pickle Deserialization
Resecurity · April 2026
- 06.Critical Unpatched Flaw Leaves Hugging Face LeRobot Open to Unauthenticated RCE
The Hacker News · April 28, 2026