Skip to content

Restrict pickle deserialization in CookieJar.load()#12091

Merged
Dreamsorcerer merged 15 commits intoaio-libs:masterfrom
YuvalElbar6:security/cookiejar-restrict-pickle
Feb 20, 2026
Merged

Restrict pickle deserialization in CookieJar.load()#12091
Dreamsorcerer merged 15 commits intoaio-libs:masterfrom
YuvalElbar6:security/cookiejar-restrict-pickle

Conversation

@YuvalElbar6
Copy link
Copy Markdown
Contributor

@YuvalElbar6 YuvalElbar6 commented Feb 18, 2026

Changes

  • _RestrictedCookieUnpickler — A pickle.Unpickler subclass that only allows cookie-related types (SimpleCookie, Morsel, defaultdict, and safe builtins). All other types (e.g. os.system, eval, exec, subprocess) raise UnpicklingError.

  • CookieJar.load() — Now uses _RestrictedCookieUnpickler instead of bare pickle.load(). Fully backward compatible with existing pickle files containing legitimate cookies.

  • CookieJar.save_json() / load_json() — New safe JSON-based alternatives for cookie persistence, immune to deserialization attacks by design.

  • Documentation — Added .. warning:: directives to save()/load() and documented the new save_json()/load_json() methods.

  • Tests — Added tests verifying:

    • os.system, eval, subprocess.call payloads are blocked
    • Legitimate cookies still load correctly (backward compat)
    • JSON roundtrip for standard, partitioned, and secure cookies
    • JSON format cannot execute code even with malicious-looking values

Test plan

  • Malicious pickle payloads (os.system, eval, exec, subprocess.call, os.popen) are all rejected with UnpicklingError
  • Existing legitimate pickled cookies load without any changes
  • test_pickle_format regression test data loads through restricted unpickler
  • save_json/load_json roundtrip preserves all cookie attributes (domain, path, secure, httponly, expires, max-age, samesite, partitioned)
  • JSON format stores code-like strings as inert data, not executable

🤖 Generated with Claude Code

CookieJar.load() previously used pickle.load() without restrictions,
allowing arbitrary code execution if an attacker could write to the
cookie jar file (e.g. shared volumes, NFS, multi-tenant environments).

This commit:
- Adds _RestrictedCookieUnpickler that only allows cookie-related
  types (SimpleCookie, Morsel, defaultdict, and safe builtins)
- Replaces pickle.load() with the restricted unpickler in load()
- Adds safe JSON-based save_json()/load_json() alternatives
- Adds security warnings to the documentation for save()/load()
- Adds tests verifying malicious payloads are blocked while
  legitimate cookies continue to load correctly

Existing pickle files with legitimate cookies are fully backward
compatible — only payloads containing non-cookie types (os.system,
eval, exec, subprocess, etc.) are rejected.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@YuvalElbar6 YuvalElbar6 changed the title Security: restrict pickle deserialization in CookieJar.load() fix restrict pickle deserialization in CookieJar.load() Feb 18, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 18, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.77%. Comparing base (291d969) to head (953e228).
⚠️ Report is 1 commits behind head on master.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #12091      +/-   ##
==========================================
+ Coverage   98.71%   98.77%   +0.05%     
==========================================
  Files         128      128              
  Lines       44907    45045     +138     
  Branches     2383     2396      +13     
==========================================
+ Hits        44332    44495     +163     
+ Misses        408      390      -18     
+ Partials      167      160       -7     
Flag Coverage Δ
CI-GHA 98.63% <100.00%> (+0.05%) ⬆️
OS-Linux 98.37% <100.00%> (+0.31%) ⬆️
OS-Windows 96.72% <100.00%> (+0.11%) ⬆️
OS-macOS 97.62% <100.00%> (?)
Py-3.10.11 97.16% <100.00%> (+1.14%) ⬆️
Py-3.10.19 97.64% <100.00%> (?)
Py-3.11.14 97.85% <100.00%> (?)
Py-3.11.9 97.38% <100.00%> (?)
Py-3.12.10 97.46% <100.00%> (?)
Py-3.12.12 97.94% <100.00%> (+0.66%) ⬆️
Py-3.13.11 97.94% <100.00%> (?)
Py-3.13.12 97.44% <100.00%> (+1.14%) ⬆️
Py-3.14.2 97.91% <100.00%> (+0.15%) ⬆️
Py-3.14.3 97.40% <100.00%> (?)
Py-3.14.3t 97.24% <100.00%> (+<0.01%) ⬆️
Py-pypy3.11.13-7.3.20 97.39% <100.00%> (?)
VM-macos 97.62% <100.00%> (?)
VM-ubuntu 98.37% <100.00%> (+0.31%) ⬆️
VM-windows 96.72% <100.00%> (+0.11%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@YuvalElbar6 YuvalElbar6 changed the title fix restrict pickle deserialization in CookieJar.load() Security: restrict pickle deserialization in CookieJar.load() Feb 18, 2026
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Feb 18, 2026

Merging this PR will not alter performance

✅ 59 untouched benchmarks


Comparing YuvalElbar6:security/cookiejar-restrict-pickle (953e228) with master (291d969)

Open in CodSpeed

YuvalElbar6 and others added 5 commits February 18, 2026 18:03
Add "partitioned" to the boolean attribute check in load_json()
so it is restored as True (bool) rather than "true" (string)
on Python 3.14+ where Morsel._reserved includes partitioned.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Add type: ignore[no-any-return] on _RestrictedCookieUnpickler.find_class
- Add type: ignore[attr-defined] on Morsel._reserved access
- Remove unused type: ignore[assignment] in load_json
- Fix __reduce__ return type annotations in test payloads
- Fix partitioned cookie test to compare attributes directly
  instead of via SimpleCookie equality (avoids Morsel re-encoding)

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Add deserialization, NFS, payloads, unpickler to the spelling
wordlist for the new CookieJar security documentation.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@YuvalElbar6 YuvalElbar6 reopened this Feb 18, 2026
@psf-chronographer psf-chronographer Bot added the bot:chronographer:provided There is a change note present in this PR label Feb 18, 2026
@YuvalElbar6 YuvalElbar6 reopened this Feb 18, 2026
@Dreamsorcerer Dreamsorcerer changed the title Security: restrict pickle deserialization in CookieJar.load() Restrict pickle deserialization in CookieJar.load() Feb 19, 2026
Comment thread aiohttp/cookiejar.py Outdated
Comment thread aiohttp/cookiejar.py Outdated
save() now writes JSON format directly. load() tries JSON first
and falls back to restricted pickle for backward compatibility
with existing cookie files.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Comment thread aiohttp/cookiejar.py Outdated
Comment thread aiohttp/cookiejar.py Outdated
Comment thread aiohttp/cookiejar.py Outdated
Comment thread aiohttp/cookiejar.py Outdated
YuvalElbar6 and others added 2 commits February 20, 2026 00:53
- Remove str() conversion for morsel attributes (already strings)
- _load_json_data() returns cookies instead of assigning internally
- Remove defensive else/defaults in JSON parsing (let invalid data fail)
- Use tuple unpacking for domain|path split

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Comment thread aiohttp/cookiejar.py Outdated
Instead of converting bool to "true" string for JSON storage
and back on load, just store the native bool values directly.
JSON handles booleans natively, so no conversion is needed.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Comment thread aiohttp/cookiejar.py Outdated
@Dreamsorcerer Dreamsorcerer added the backport-3.14 Trigger automatic backporting to the 3.14 release branch by Patchback robot label Feb 20, 2026
@Dreamsorcerer Dreamsorcerer merged commit 8a631e7 into aio-libs:master Feb 20, 2026
44 checks passed
@patchback
Copy link
Copy Markdown
Contributor

patchback Bot commented Feb 20, 2026

Backport to 3.14: 💚 backport PR created

✅ Backport PR branch: patchback/backports/3.14/8a631e74c1d266499dbc6bcdbc83c60f4ea3ee3c/pr-12091

Backported as #12105

🤖 @patchback
I'm built with octomachinery and
my source is open — https://github.com/sanitizers/patchback-github-app.

patchback Bot pushed a commit that referenced this pull request Feb 20, 2026
---------

Co-authored-by: Sam Bull <[email protected]>
(cherry picked from commit 8a631e7)
@Dreamsorcerer
Copy link
Copy Markdown
Member

@YuvalElbar6 Could you submit a followup PR to master that removes the pickle loading, and any references to pickling in the docs?

@YuvalElbar6
Copy link
Copy Markdown
Contributor Author

@Dreamsorcerer yes I will

YuvalElbar6 added a commit to YuvalElbar6/aiohttp that referenced this pull request Feb 21, 2026
Follow-up to aio-libs#12091. Remove mentions of pickle format from the
CookieJar.load() docstring and the client_reference docs.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Dreamsorcerer added a commit that referenced this pull request Feb 22, 2026
…n CookieJar.load() (#12105)

**This is a backport of PR #12091 as merged into master
(8a631e7).**

---------

Co-authored-by: Yuval Elbar <[email protected]>
Co-authored-by: Sam Bull <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-3.14 Trigger automatic backporting to the 3.14 release branch by Patchback robot bot:chronographer:provided There is a change note present in this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants