40 Bytes to Chaos
CVE-2026-33155 · GHSA-54jj-px8x-5w5q · CWE-400 · HIGH 8.7
How we found a memory exhaustion vulnerability in DeepDiff that can crash a server with a payload smaller than a tweet - and why millions of Python applications are in its blast radius.
.png)
Background
DeepDiff is one of those libraries you probably haven't heard of - but your code almost certainly uses it. It's a Python utility for comparing objects: dictionaries, lists, data classes, nested structures. It's simple, useful, and downloaded 29 million times a month from PyPI. It is also present in Amazon-related tooling, meaning many teams may be exposed without realising it. Given its widespread adoption across the broader ML ecosystem, the potential blast radius of this vulnerability extends to thousands of applications and the organisations that rely on them.
In 2025, a serious vulnerability was found in DeepDiff's serialisation mechanism: the Delta class could be exploited via malicious pickle payloads to achieve remote code execution and object pollution (CVE-2025-58367). The maintainers responded promptly - converting the SAFE_TO_IMPORT allowlist to a frozenset and hardening the _RestrictedUnpickler against class traversal. The intent was to make deserialising Delta objects from untrusted sources safe.
During a supply chain audit of our own codebase at Periphery, we found that the patch left something behind.
Discovery
Our process started with a systematic review of third-party dependencies. We weren't hunting for anything specific - we were doing the kind of routine supply chain hygiene that, frankly, more teams should build into their development cycles. We scanned our imports, traced data flows to and from deserialisation boundaries, and started looking closely at anything that touched pickle.
DeepDiff came up. We knew about the prior CVE. We pulled the patched version, read the fix, and noticed something.
The restricted unpickler validates which classes can be loaded. It does not validate what is passed to their constructors.
The allowlist - SAFE_TO_IMPORT - includes types like builtins.bytes, builtins.list, and builtins.range. These are permitted because they're considered safe from a code execution standpoint. They are. But they share a property that the patch didn't account for: their constructors allocate memory proportional to their input. Pass bytes(10_000_000_000) and you get a 10 GB allocation. The restricted unpickler doesn't override the REDUCE opcode and doesn't inspect what's passed to constructors before they execute.
We had a hypothesis. We built a payload to test it.
Finding: A 42-byte pickle payload forces over 10 GB of memory allocation during deserialisation. The allocation happens before any application-level logic runs. The process crashes or is killed by the OS.
The Vulnerability in Detail
Root Cause
The _RestrictedUnpickler.find_class method gates class loading against the allowlist. What it does not do is intercept the REDUCE opcode or inspect constructor arguments. This means any class on the allowlist can be instantiated with arbitrary arguments - including arguments that trigger unbounded memory allocation.
The relevant check in serialization.py (line 353):
def find_class(self, module, name):
if f"{module}.{name}" not in SAFE_TO_IMPORT:
raise UnpicklingError(
f"Attempting to unpickle unsafe class: {module}.{name}"
)
return super().find_class(module, name)
The class is checked. The arguments passed to it are not. The door is locked but the window is open.
Exploitation Path 1 - Raw pickle_load
The simplest attack. A pickle payload that calls builtins.bytes(N) with a very large integer. The allocation happens during deserialisation, before any delta processing begins.
GLOBAL builtins.bytes # passes find_class — it's on the allowlist
INT 10000000000 # 10 billion
TUPLE + REDUCE # → bytes(10**10) → ~9.3 GB allocated instantly
Payload size: 42 bytes. Memory allocated: ~9.3 GB. Amplification: ~2,000,000×.
Exploitation Path 2 - Delta application
A more subtle path, exploitable without raw pickle knowledge. It works by crafting a valid diff dictionary that first sets a value to a large integer via values_changed, then converts it to bytes via type_changes.
The ordering is key. In delta.py line 183, Delta.add() processes _do_values_changed() before _do_type_changes(). Step one writes a large integer into the target object in place. Step two reads that modified value and calls new_type(current_old_value) at line 576 - with no size guard.
payload_dict = {
'values_changed': {"root['x']": {'new_value': 10**8}},
'type_changes': {"root['x']": {'new_type': bytes}},
}
# Step 1: x = 100_000_000 (written by _do_values_changed)
# Step 2: x = bytes(100_000_000) → 95 MB allocated (at 10**10: ~9.3 GB)
Proof of Concept
We used Python's resource module to cap memory at 1 GB during internal testing so we could reproduce safely without hitting the OOM killer. Even at the capped scale, the numbers are stark:
[*] Memory limit set to 1024 MB
Payload size: 123 bytes
Allocated: 95 MB
Amplification: 813,008×
Payload size: 42 bytes
Allocated: 95 MB
Amplification: ~2,000,000×
At full scale - removing the cap and using 10**10 - both paths allocate over 9 GB. It was enough to take out our own servers at Periphery HQ during testing. The OOM killer doesn't discriminate.
How an Adversary Exploits This
The exploit itself is trivial - 42 bytes is not a sophisticated payload. The barrier is finding a delivery path to a deserialisation sink.
Path 1 - Direct API endpoint
An adversary identifies an endpoint that accepts serialised Delta objects and sends the payload in the request body. The server calls pickle_load(request.body) before any auth logic runs. The process allocates 9.3 GB and is killed by the OS. No authentication bypass required - the vulnerability fires at deserialisation time, before application logic begins.
Path 2 - Message queue poisoning (most dangerous)
An adversary gains write access to a Kafka, RabbitMQ, or SQS topic via compromised credentials, SSRF, or a misconfigured queue policy. They publish the malicious Delta payload once. The consumer crashes on receipt, auto-restarts, and crashes again on the same message. The payload persists in the queue - producing a permanent crash loop with a single delivery. No ongoing connection required.
The persistence problem: Safely draining the queue without crashing another consumer requires careful manual intervention. In a high-throughput system with multiple consumer groups, this can stall an entire pipeline for hours.
Path 3 - File upload
An adversary uploads a malicious .pkl or .delta artifact - framed as a model file, config export, or dataset diff. A background worker processes it asynchronously. The worker crashes silently, with no immediate error surfaced. The pipeline stalls and monitoring may not alert for minutes or longer.
Scale of Exposure
29 million downloads a month is a large number, but download counts are an imperfect metric. What we can verify directly: at least 521 repositories and 896 packages explicitly declare DeepDiff as a dependency. The real number is likely significantly higher - but we will only publish what we can stand behind. Among the projects with verified public dependency declarations: Amazon Web Services (via the SageMaker Python SDK and AWS Service Catalog Puppet), Lightning AI, and DataHub.
Anyone running these platforms inherits the vulnerability without necessarily knowing DeepDiff is present in their stack. The attack surface is orders of magnitude larger than the library's own download numbers suggest.
Thinking Through the Risk
Here is a rewritten standalone section that replaces both the old "Addressing the Sceptics" and "Scale of Exposure" and trims the "False Patch Problem" overlap, without repeating anything already covered earlier in the blog:
Thinking Through the Risk
Every significant vulnerability disclosure attracts the same set of reasonable questions. Rather than dismiss them, it is worth working through them properly, because the answers reveal why this finding matters more than it might first appear.
The first instinct is often to ask whether exploitation requires existing access. To a degree, yes. But the relevant question is not whether pickle_load is exposed to the internet. It is whether any data from outside a fully trusted boundary ever reaches it. In most non-trivial Python architectures, it does, and the teams responsible often have no idea, because the deserialisation happens inside a dependency they did not knowingly install.
The second instinct is to minimise a denial of service finding relative to RCE or data exfiltration. That instinct is reasonable in most cases. Here it misses something. A 42-byte payload achieving 2,000,000x amplification is not a volumetric attack - it is a single message. And if that message lands in a queue or file store, the crash is not a one-time event — the service restarts, picks up the same payload, and crashes again until someone manually intervenes. The more important question is what else becomes possible while the monitoring pipeline is down.
The most substantive challenge is the user error argument: unpickling untrusted data has always been dangerous, and Python's documentation says so plainly. This is worth sitting with. But DeepDiff did not simply use pickle and leave the risk to the developer. It shipped a dedicated safety control, explicitly hardened after CVE-2025-58367, designed to make Delta deserialisation safer. Developers who relied on that control were not being reckless. They were trusting a mechanism the library itself told them was there. The incomplete control is the vulnerability, not the decision to use the library.
Finally, the patch is available and the maintainers moved fast. But for most affected deployments, DeepDiff is not a direct dependency. It arrives silently through DataHub, the AWS SageMaker SDK, or other upstream packages, and it will not be updated until that upstream maintainer acts. Any team that considers themselves protected after patching CVE-2025-58367 should verify. The restricted unpickler they updated to was the fix for that issue. It was not a fix for this one.
The False Patch Problem
This deserves its own section because it's the most underappreciated risk in this disclosure.
CVE-2025-58367 was serious - class pollution, RCE, the kind of finding that triggers emergency patching cycles. Organisations that responded correctly updated DeepDiff and moved on, reasonably confident the serialisation attack surface had been addressed. The restricted unpickler was the fix.
This CVE shows the fix was incomplete. Any organisation that considers itself patched after CVE-2025-58367 and has not yet updated to 8.6.2 has a false sense of security around this exact code path. For organisations with long patch cycles - common in enterprise, OT/ICS, and regulated environments - that window of exposure may already have been open for months.
Disclosure Timeline
- 16 March 2026 - Periphery start auditting software stack supply chain
- 18 March 2026, morning - Periphery's supply chain audit flags DeepDiff; restricted unpickler identified as an incomplete security control
- 18 March 2026 - PoC developed and confirmed across both raw pickle and Delta application paths
- 18 March 2026 - Findings and PoC disclosed to DeepDiff maintainers via GitHub private security advisory
- 18 March 2026, 20:10 UTC -DeepDiff 8.6.2 patch committed and advisory GHSA-54jj-px8x-5w5q published by maintainer seperman
- 18 March 2026, 20:16 UTC - CVE-2026-33155 assigned and pushed to the GitHub Advisory Database
- 19 March 2026 - Public disclosure.
Remediation
Update DeepDiff to 8.6.2 or later immediately. If you manage any upstream package that depends on DeepDiff, pin to >=8.6.2 and cut a release.
If you cannot update immediately, treat all DeepDiff deserialisation calls as a high-risk operation and ensure no path exists for untrusted data to reach pickle_load() or the Delta constructor. This is an architectural control, not a code-level fix - the restricted unpickler cannot be made safe by configuration alone.
If you are using any of the following, check your DeepDiff version now:
- Amazon SageMaker or the sagemaker-python-sdk
- DataHub or the
acryl-datahubpackage - MLflow, SageMaker, or downstream ML lifecycle tooling
- Any ETL or data synchronisation framework that processes external artifacts
- Any application that accepts serialised data diffs from a network source
This vulnerability was discovered by the team at Periphery Security during routine supply chain auditing. We are credited in the official advisory at github.com/adv
isories/GHSA-54jj-px8x-5w5q. We thank the DeepDiff maintainers for their swift and professional response to our disclosure.






