security: do not auto-select code-executing formats (pickle, dill) from a file extension - #14
Open
hackchang wants to merge 1 commit into
Open
security: do not auto-select code-executing formats (pickle, dill) from a file extension#14hackchang wants to merge 1 commit into
hackchang wants to merge 1 commit into
Conversation
…om a file extension serialize.load(path) / dump(obj, path) chose the backend purely from the file extension, so a .pickle/.dill path was silently routed to pickle/dill (which execute code on load). A caller that only meant to read data could reach Unpickler.load() if the path/extension was attacker-influenced (CWE-502). Mark such formats unsafe (register_format(..., unsafe=True)); they are never auto-selected from an extension and require an explicit fmt=. Data formats are unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
hackchang
force-pushed
the
fix/no-code-exec-autoroute
branch
from
August 13, 2026 01:12
9c97569 to
fa0ce21
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR: security — don't auto-select code-executing formats (pickle, dill) from a file extension
What this fixes
serialize.load(path)/serialize.dump(obj, path)pick the backend purely from the file extension(
_get_format_from_ext). Becausepickleanddillare registered with the.pickle/.dillextensions,a caller who only intends to read data (
serialize.load(user_path)) is silently routed topickle.Unpickler(...).load()/dill.Unpickler(...).load()— which execute arbitrary code — whenever thepath/extension is attacker-influenced. (CWE-502, Deserialization of Untrusted Data.)
This implements exactly the "explicit flag" hardening we discussed: code-executing backends are marked
unsafe=Trueand are never auto-selected from an extension; a caller must passfmt=explicitly to usethem. Data formats (json, yaml, msgpack, …) are unchanged and keep auto-detecting.
Changes
all.py: newUNSAFE_FORMATSset;register_format(..., unsafe=False);_get_format_from_extraises aclear
ValueError(telling the caller to passfmt=) if the extension maps to an unsafe format.pickle.py,dill.py:register_format(..., unsafe=True).testsuite/test_basic.py: the extension/auto-detection tests now assert the new behavior for unsafe formats.testsuite/test_security.py: new regression tests (pickle/dill refused by extension, explicitfmt=stillworks, safe formats still auto-detect).
serialize.load(path, fmt="pickle")still works for the trusted-input case.Test suite: unchanged pass/fail vs
mainontest_basic.py(the pre-existingtest_round_tripfailures areunrelated), plus the new
test_security.pypasses.Note on the YAML backend (the second vector in the report — your call on design)
The report also covered the
yamlbackends:serialize/yaml.pyandserialize/yaml_legacy.pyload with thefull, unsafe
yaml.Loader, so a.yamldocument containing!!python/object/apply:os.system [...]executescode. I deliberately left the yaml change out of this PR because a clean fix needs a design decision that's
yours to make (you mentioned wanting a better API here). What I found while testing:
yaml.FullLoaderblocks the RCE tags (!!python/object/apply/new/module) while still supporting!!python/tupleetc.yaml_legacyworks perfectly underFullLoader(all round-trips pass, RCE blocked).yamlbackend serializes registered classes as!!python/object:...(the default dumper path),which
FullLoader/SafeLoaderreject — so making it safe means changing how custom types are encoded(e.g. emit only the
SERIALIZED_TAGmapping + a safe dumper), which is a wire-format decision.SafeLoaderis fully safe but drops the ability to serialize arbitrary unregistered objects via yaml (thatcapability is what makes the RCE possible); only classes registered with
register_classwould round-trip.Happy to send a follow-up PR for the yaml backend in whichever direction you prefer (FullLoader now + a safe
dumper later, or a stricter SafeLoader + register_class-only path). Just let me know.
Reported/authored by hackchang.