Python: fix(core): restrict unpickler module-prefix allowlist to types only#5923
Open
White-Mouse wants to merge 1 commit into
Open
Python: fix(core): restrict unpickler module-prefix allowlist to types only#5923White-Mouse wants to merge 1 commit into
White-Mouse wants to merge 1 commit into
Conversation
The `_RestrictedUnpickler.find_class` method allows any attribute from modules matching the `agent_framework.` and `openai.types.` prefixes. This permits non-type globals such as functions and sub-modules, which can be chained through `builtins.getattr` to reach `pickle.loads` and execute unrestricted nested pickle payloads. Limit module-prefix allowlisting to actual types by checking that the resolved global is an instance of `type` before returning it. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Author
|
@microsoft-github-policy-service agree |
Contributor
There was a problem hiding this comment.
Pull request overview
Tightens the Python checkpoint restricted unpickling boundary by preventing agent_framework.* and openai.types.* module-prefix allowlisting from returning non-type globals (e.g., functions/modules), mitigating gadget chains that can pivot via builtins.getattr.
Changes:
- Updates
_RestrictedUnpickler.find_classto resolve globals under allowed prefixes and permit them only if they are actualtypeobjects. - Adds a dedicated
UnpicklingErrormessage for blocked non-type globals from allowed prefixes.
Comment on lines
99
to
104
| def find_class(self, module: str, name: str) -> type: | ||
| type_key = f"{module}:{name}" | ||
|
|
||
| if ( | ||
| type_key in _BUILTIN_ALLOWED_TYPE_KEYS | ||
| or type_key in self._allowed_types | ||
| or module.startswith(_FRAMEWORK_MODULE_PREFIX) | ||
| or module.startswith(_OPENAI_MODULE_PREFIX) | ||
| ): | ||
| if type_key in _BUILTIN_ALLOWED_TYPE_KEYS or type_key in self._allowed_types: | ||
| return super().find_class(module, name) # type: ignore[no-any-return] # nosec | ||
|
|
Comment on lines
+102
to
104
| if type_key in _BUILTIN_ALLOWED_TYPE_KEYS or type_key in self._allowed_types: | ||
| return super().find_class(module, name) # type: ignore[no-any-return] # nosec | ||
|
|
Comment on lines
+105
to
+118
| if module.startswith(_FRAMEWORK_MODULE_PREFIX) or module.startswith( | ||
| _OPENAI_MODULE_PREFIX | ||
| ): | ||
| resolved = super().find_class(module, name) # nosec | ||
| # Reject non-type globals from allowed package prefixes. A broad | ||
| # module-prefix allowlist returns arbitrary module attributes such | ||
| # as functions and sub-modules, which can be combined with | ||
| # builtins.getattr to call unrestricted pickle.loads. | ||
| if isinstance(resolved, type): | ||
| return resolved # type: ignore[return-value] | ||
| raise pickle.UnpicklingError( | ||
| f"Checkpoint deserialization blocked for non-type global " | ||
| f"'{type_key}'." | ||
| ) |
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.
Summary
_RestrictedUnpickler.find_classcurrently allows any global from modules matching theagent_framework.oropenai.types.prefixes. This can return non-type attributes (functions, sub-modules) which may be chained throughbuiltins.getattrto bypass the restricted deserialization boundary.Problem
The module-prefix check in
find_classgrants blanket access to all globals in framework and OpenAI SDK modules:Since
builtins:getattris in the built-in allowlist, a crafted checkpoint payload can chain through a non-type global from an allowed package prefix to reachpickle.loads, bypassing the restricted unpickler entirely.Fix
Resolve the global first, then only return it if it is actually a
type:Non-type globals (functions, modules) are rejected with a clear error message.
Backwards Compatibility
All legitimate class/type references from
agent_framework.*andopenai.types.*continue to work. Only non-type globals are rejected, which were never valid deserialization targets.🤖 Generated with Claude Code