# GHSA-wccx-j62j-r448: Fickling has `always_check_safety()` bypass: pickle.loads and _pickle.loads remain unhooked

> Fickling fails to hook all pickle entry points, allowing attackers to execute arbitrary code via deserialization despite safety checks being enabled.

- **Severity:** Not scored
- **Exploited in the wild:** Not known
- **Login needed:** Not needed
- **Attackable:** Over the internet
- **Victim action needed:** Not needed
- **Fix available:** Yes
- **Published:** 4 Mar 2026

## What is GHSA-wccx-j62j-r448?

GHSA-wccx-j62j-r448 is an unsafe deserialization vulnerability in fickling, published on 4 Mar 2026. An attacker can exploit it over the internet without logging in, with no action from the victim.

### Official 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.load`
- `pickle.Unpickler`
- `_pickle.Unpickler`

It does not patch:
- `pickle.loads`
- `_pickle.load`
- `_pickle.loads`

## Reproduction (clean upstream)
```python
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
```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.

## Which versions are affected by GHSA-wccx-j62j-r448?

2 open source packages ship an affected version, including fickling.


| Package | Ecosystem | Affected | Fixed in |
| --- | --- | --- | --- |
| `fickling` | PyPI | All versions | 0.1.9 |
| `fickling` | PyPI | All versions | 0.1.9 |

## How do I fix GHSA-wccx-j62j-r448?

Upgrade fickling to 0.1.9 or later.

## Is GHSA-wccx-j62j-r448 being exploited?

There is no known exploitation of GHSA-wccx-j62j-r448 in the wild and no public exploit code.

## Fix prompt for AI coding agents

```text
Check this codebase for GHSA-wccx-j62j-r448 and fix it if it is affected.

What it is: GHSA-wccx-j62j-r448 is an unsafe deserialization vulnerability in fickling, published on 4 Mar 2026. An attacker can exploit it over the internet without logging in, with no action from the victim.
Exploitation: There is no known exploitation of GHSA-wccx-j62j-r448 in the wild and no public exploit code.

Affected packages and fixed versions:
- fickling (PyPI): affected all versions; fixed in 0.1.9
- fickling (PyPI): affected all versions; fixed in 0.1.9

Task:
1. Search the dependency manifests and lockfiles (for example package.json, package-lock.json, requirements.txt, poetry.lock, uv.lock, pom.xml, build.gradle, go.mod, Cargo.lock, Gemfile.lock) and any Dockerfiles for the affected packages, including transitive dependencies.
2. If an affected version is in use, upgrade it to the fixed version for its release line with the project's package manager, so the lockfile is regenerated. Prefer the smallest upgrade that fixes it.
3. If there is no fix, or the upgrade would break the build, do not guess: explain the risk and the safest workaround instead.
4. Run the tests and summarise what changed and why.
5. If nothing in the project is affected, say so and change nothing.

Reference: https://insights.sbomify.com/GHSA-wccx-j62j-r448/ (Markdown: https://insights.sbomify.com/GHSA-wccx-j62j-r448/index.md)
```

## Sources

- OSV: https://osv.dev/vulnerability/GHSA-wccx-j62j-r448

---
From sbomify Insights: https://insights.sbomify.com/GHSA-wccx-j62j-r448/
Summaries are generated and may contain mistakes; the official description above is the record.
