Fickling has `always_check_safety()` bypass: pickle.loads and _pickle.loads remain unhooked
Assessment
The missing pickle entrypoints pickle.loads, _pickle.loads, and _pickle.load were added to the hook https://github.com/trailofbits/fickling/commit/8c24c6edabceab156cfd41f4d70b650e1cdad1f7.
Original report
Summary
fickling.always_check_safety() does not hook all pickle entry points. pickle.loads, _pickle.loads, and _pickle.load remain unprotected, enabling malicious payload execution despite global safety mode being enabled.
Affected versions
<= 0.1.8 (verified on current upstream HEAD as of 2026-03-03)
Non-duplication check against published Fickling GHSAs
No published advisory covers hook-coverage bypass in run_hook().
Existing advisories are blocklist/detection bypasses (runpy, pty, cProfile, marshal/types, builtins, network constructors, OBJ visibility, etc.), not runtime hook coverage parity.
Root cause
run_hook() patches only:
pickle.loadpickle.Unpickler_pickle.Unpickler
It does not patch:
pickle.loads_pickle.load_pickle.loads
Reproduction (clean upstream)
import io, pickle, _pickle
from unittest.mock import patch
import fickling
from fickling.exception import UnsafeFileError
class Payload:
def __reduce__(self):
import subprocess
return (subprocess.Popen, (['echo','BYPASS'],))
data = pickle.dumps(Payload())
fickling.always_check_safety()
# Bypass path
with patch('subprocess.Popen') as popen_mock:
pickle.loads(data)
print('bypass sink called?', popen_mock.called) # True
# Control path is blocked
with patch('subprocess.Popen') as popen_mock:
try:
pickle.load(io.BytesIO(data))
except UnsafeFileError:
pass
print('blocked sink called?', popen_mock.called) # False
Observed on vulnerable code:
pickle.loadsexecutes payloadpickle.loadis blocked
Minimal patch diff
--- a/fickling/hook.py
+++ b/fickling/hook.py
@@
def run_hook():
- pickle.load = loader.load
+ pickle.load = loader.load
+ _pickle.load = loader.load
+ pickle.loads = loader.loads
+ _pickle.loads = loader.loads
Validation after patch
pickle.loads,_pickle.loads, and_pickle.loadall raiseUnsafeFileError- sink not called in any path
Regression tests added locally:
test_run_hook_blocks_pickle_loadstest_run_hook_blocks__pickle_load_and_loadsintest/test_security_regressions_20260303.py
Impact
High-confidence runtime protection bypass for applications that trust always_check_safety() as global guard.
Affected software
Click a technology to see all vulnerabilities affecting it.
| Platform | Package | Vulnerable versions | Fixed in |
|---|---|---|---|
| Python (PyPI) | fickling | < 0.1.9 | 0.1.9 |
References
- CVSS vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:P- Weakness (CWE)
CWE-693- Platform
- Python (PyPI)
- Package
fickling- Disclosed
- 04.03.2026
- Updated
- 11.10.2026 12:19
Sources and licenses
OSV / GitHub Advisory Database - GHSA-wccx-j62j-r448 - CC BY 4.0
Vulnerability history: fickling
All published vulnerabilities of this package (Python (PyPI)), newest first. Shows whether this is a one-off slip or a package that needs watching.
- Total entries
- 16
- First → latest
- 12.2025 07.2026
- Without a fix
- 0
- Exploited in attacks
- 0
Severity breakdown
- Critical 1
- High 11
- Medium 3
- Low 1
Disclosures per year
- 2025: 2
- 2026: 14
-
Fickling has `always_check_safety()` bypass: pickle.loads and _pickle.loads remain unhooked this entry
Know which vulnerabilities affect your application
With the DockRay integration and software inventory enabled, DockRay checks reported component versions against known vulnerabilities and notifies you about confirmed matches. Coverage depends on the supported technology and available advisory data.