GHSA-wccx-j62j-r448 — fickling
Fix: trailofbits/fickling@8c24c6eGHSA-wccx-j62j-r448 is a security vulnerability in fickling. A fix is available for fickling — see the affected versions and patch details below.
Fickling has `always_check_safety()` bypass: pickle.loads and _pickle.loads remain unhooked
Real-World Exposure
ficklingReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.
Description
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 Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐍PyPI | fickling | all versions | 0.1.9pip install --upgrade 'fickling==0.1.9' |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for fickling, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update fickling to 0.1.9 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-wccx-j62j-r448 is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like GHSA-wccx-j62j-r448 can be triaged on real exposure rather than presence alone.
Tailored to GHSA-wccx-j62j-r448. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-wccx-j62j-r448 in your dependencies?
O3 Security finds GHSA-wccx-j62j-r448 across PyPI dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.