Severity by source
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Failure of fickling's safety gate lets an attacker-supplied pickle achieve full code execution during automated ML model loading, so AV:N/AC:L/PR:N/UI:N with total C/I/A impact.
Primary rating from Vendor (BombadilSystems).
CVSS VectorNVD
Lifecycle Timeline
7Blast Radius
ecosystem impact- 62 pypi packages depend on fickling (10 direct, 53 indirect)
Ecosystem-wide dependent count for version 0.1.12.
DescriptionNVD
In Trail of Bits fickling versions up to and including 0.1.11, the UnsafeImportsML analysis pass unconditionally calls AnalysisContext.shorten_code(node) on every import node it inspects, regardless of whether the import is flagged as unsafe. This call registers the shortened code representation in the shared AnalysisContext.reported_shortened_code set. When the MLAllowlist analysis pass subsequently runs, it calls the same shorten_code() method, receives already_reported=True for every import, and executes a continue statement that skips its allowlist check entirely. This renders MLAllowlist dead code for all imports - it never evaluates whether an import is in the ML allowlist or not. The MLAllowlist pass was designed to catch imports of modules outside the known-safe ML ecosystem (torch, numpy, transformers, etc.) that slip past the UnsafeImports denylist. With MLAllowlist inoperative, any standard library module not in the UNSAFE_IMPORTS denylist can be invoked via pickle deserialization while fickling's check_safety() returns LIKELY_SAFE. The fickling.load() API chains check_safety() into pickle.loads() as an explicit security gate, meaning a LIKELY_SAFE verdict causes the payload to be deserialized and executed. The root cause is shared mutable state between independently-correct analysis passes - UnsafeImportsML works as designed in isolation, MLAllowlist works as designed in isolation, but the shared reported_shortened_code set causes UnsafeImportsML to poison MLAllowlist's deduplication logic.
AnalysisAI
Security-control bypass in Trail of Bits fickling (≤0.1.11) neuters its MLAllowlist analysis pass so that malicious pickle files pass fickling's check_safety() gate as LIKELY_SAFE, enabling arbitrary code execution when fickling.load() deserializes them. Because UnsafeImportsML pre-registers every import in the shared reported_shortened_code set, MLAllowlist always short-circuits and never validates imports against the known-safe ML ecosystem, so any standard-library module outside the UNSAFE_IMPORTS denylist can be smuggled through. Publicly available exploit code exists (SSVC 'poc'); it is not listed in CISA KEV and EPSS is low (0.30%), consistent with a demonstrated-but-not-yet-widespread threat.
Technical ContextAI
fickling is Trail of Bits' pickle security and reverse-engineering tool used to statically screen Python pickle streams (commonly ML model files) before deserialization. Per CPE cpe:2.3:a:trailofbits:fickling, all versions through 0.1.11 are affected. Its safety verdict is produced by a chain of independent Analysis passes over the pickle AST. Two passes matter here: UnsafeImportsML flags imports on a denylist, and MLAllowlist is the positive control meant to reject imports NOT in the known-safe ML allowlist (torch, numpy, transformers, etc.). Both share a single AnalysisContext.reported_shortened_code deduplication set. UnsafeImportsML unconditionally called shorten_code(node) on every import, registering it in that set; MLAllowlist then saw already_reported=True and executed 'continue', skipping its allowlist check for every import. This maps to CWE-693 (Protection Mechanism Failure): each pass is individually correct, but shared mutable state silently disables the second, defeating the layered defense rather than corrupting memory or logic within a single component.
RemediationAI
Vendor-released patch: 0.1.12-upgrade fickling to 0.1.12 or later (pip install --upgrade 'fickling>=0.1.12'), which refactors shorten_code() into a pure formatter plus an explicit mark_reported() call and unregisters MLAllowlist from automatic dedup so it always runs; see PR https://github.com/trailofbits/fickling/pull/278, commit 41ce7cb01edd97072994039574a2301ebb3f463d, and release https://github.com/trailofbits/fickling/releases/tag/v0.1.12. Until you can upgrade, do not treat a fickling LIKELY_SAFE verdict as authoritative on ≤0.1.11: refuse to load pickles from untrusted or unauthenticated sources, and prefer non-pickle formats such as safetensors where feasible-trade-off is that this blocks legitimate pickle-based workflows and may require converting existing model artifacts. As a stronger compensating control, deserialize any untrusted pickle only inside a sandboxed, network-isolated, least-privilege process (restricted syscalls/filesystem, no outbound egress) so a bypassed check cannot escalate to full host compromise; the trade-off is added operational complexity and the sandbox itself must be correctly locked down.
Fickling's static analyzer through version 0.1.6 fails to properly classify the cProfile module as unsafe during pickle
Fickling's incomplete pickle analysis allows attackers to bypass security checks by using Python's runpy module to execu
Fickling's static analyzer before version 0.1.7 fails to detect several dangerous Python modules in pickled objects, ena
Arbitrary code execution bypass in Trail of Bits fickling (versions ≤ 0.1.10) allows attackers to craft malicious pickle
Fickling versions prior to 0.1.7 fail to properly detect malicious pickle payloads due to inadequate handling of the "bu
Fickling before version 0.1.7 allows local attackers to achieve arbitrary code execution through Python pickle deseriali
Same weakness CWE-693 – Protection Mechanism Failure
View allSame technique Deserialization
View allVendor StatusVendor
Share
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-41676
GHSA-mgx3-9w7v-8674