VYPR
Critical severityOSV Advisory· Published Sep 5, 2025· Updated Apr 15, 2026

CVE-2025-58367

CVE-2025-58367

Description

DeepDiff is a project focused on Deep Difference and search of any Python data. Versions 5.0.0 through 8.6.0 are vulnerable to class pollution via the Delta class constructor, and when combined with a gadget available in DeltaDiff, it can lead to Denial of Service and Remote Code Execution (via insecure Pickle deserialization) exploitation. The gadget available in DeepDiff allows deepdiff.serialization.SAFE_TO_IMPORT to be modified to allow dangerous classes such as posix.system, and then perform insecure Pickle deserialization via the Delta class. This potentially allows any Python code to be executed, given that the input to Delta is user-controlled. Depending on the application where DeepDiff is used, this can also lead to other vulnerabilities. This is fixed in version 8.6.1.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
deepdiffPyPI
>= 5.0.0, < 8.6.18.6.1

Affected products

1

Patches

5
60ac5b903dbd

Merge commit from fork

https://github.com/seperman/deepdiffSep DehpourSep 3, 2025via osv
16 files changed · +178 14
  • AUTHORS.md+1 0 modified
    @@ -75,3 +75,4 @@ Authors in order of the timeline of their contributions:
     - [dtorres-sf](https://github.com/dtorres-sf) for the fix for moving nested tables when using iterable_compare_func.
     - [Jim Cipar](https://github.com/jcipar) for the fix recursion depth limit when hashing numpy.datetime64
     - [Enji Cooper](https://github.com/ngie-eign) for converting legacy setuptools use to pyproject.toml
    +- [Diogo Correia](https://github.com/diogotcorreia) for reporting security vulnerability in Delta and DeepDiff that could allow remote code execution.
    
  • .bumpversion.cfg+1 1 modified
    @@ -1,5 +1,5 @@
     [bumpversion]
    -current_version = 8.6.0
    +current_version = 8.6.1
     commit = True
     tag = True
     tag_name = {new_version}
    
  • CHANGELOG.md+4 0 modified
    @@ -1,5 +1,9 @@
     # DeepDiff Change log
     
    +- v8-6-1
    +    - Patched security vulnerability in the Delta class which was vulnerable to class pollution via its constructor, and when combined with a gadget available in DeltaDiff itself, it could lead to Denial of Service and Remote Code Execution (via insecure Pickle deserialization).
    +
    +
     - v8-6-0
         - Added Colored View thanks to @mauvilsa 
         - Added support for applying deltas to NamedTuple thanks to @paulsc 
    
  • CITATION.cff+1 1 modified
    @@ -5,6 +5,6 @@ authors:
       given-names: "Sep"
       orcid: "https://orcid.org/0009-0009-5828-4345"
     title: "DeepDiff"
    -version: 8.6.0
    +version: 8.6.1
     date-released: 2024
     url: "https://github.com/seperman/deepdiff"
    
  • deepdiff/delta.py+7 1 modified
    @@ -17,7 +17,7 @@
     )
     from deepdiff.path import (
         _path_to_elements, _get_nested_obj, _get_nested_obj_and_force,
    -    GET, GETATTR, parse_path, stringify_path,
    +    GET, GETATTR, check_elem, parse_path, stringify_path,
     )
     from deepdiff.anyset import AnySet
     from deepdiff.summarize import summarize
    @@ -237,6 +237,11 @@ def _get_elem_and_compare_to_old_value(
             forced_old_value=None,
             next_element=None,
         ):
    +        try:
    +            check_elem(elem)
    +        except ValueError as error:
    +            self._raise_or_log(UNABLE_TO_GET_ITEM_MSG.format(path_for_err_reporting, error))
    +            return not_found
             # if forced_old_value is not None:
             try:
                 if action == GET:
    @@ -536,6 +541,7 @@ def _get_elements_and_details(self, path):
                     obj = self
                     # obj = self.get_nested_obj(obj=self, elements=elements[:-1])
                 elem, action = elements[-1]  # type: ignore
    +            check_elem(elem)
             except Exception as e:
                 self._raise_or_log(UNABLE_TO_GET_ITEM_MSG.format(path, e))
                 return None
    
  • deepdiff/__init__.py+1 1 modified
    @@ -1,6 +1,6 @@
     """This module offers the DeepDiff, DeepSearch, grep, Delta and DeepHash classes."""
     # flake8: noqa
    -__version__ = '8.6.0'
    +__version__ = '8.6.1'
     import logging
     
     if __name__ == '__main__':
    
  • deepdiff/path.py+7 0 modified
    @@ -117,6 +117,7 @@ def _path_to_elements(path, root_element=DEFAULT_FIRST_ELEMENT):
     
     def _get_nested_obj(obj, elements, next_element=None):
         for (elem, action) in elements:
    +        check_elem(elem)
             if action == GET:
                 obj = obj[elem]
             elif action == GETATTR:
    @@ -134,11 +135,17 @@ def _guess_type(elements, elem, index, next_element):
         return {}
     
     
    +def check_elem(elem):
    +    if isinstance(elem, str) and elem.startswith("__") and elem.endswith("__"):
    +        raise ValueError("traversing dunder attributes is not allowed")
    +
    +
     def _get_nested_obj_and_force(obj, elements, next_element=None):
         prev_elem = None
         prev_action = None
         prev_obj = obj
         for index, (elem, action) in enumerate(elements):
    +        check_elem(elem)
             _prev_obj = obj
             if action == GET:
                 try:
    
  • deepdiff/serialization.py+2 2 modified
    @@ -59,7 +59,7 @@ class UnsupportedFormatErr(TypeError):
     DELTA_IGNORE_ORDER_NEEDS_REPETITION_REPORT = 'report_repetition must be set to True when ignore_order is True to create the delta object.'
     DELTA_ERROR_WHEN_GROUP_BY = 'Delta can not be made when group_by is used since the structure of data is modified from the original form.'
     
    -SAFE_TO_IMPORT = {
    +SAFE_TO_IMPORT = frozenset({
         'builtins.range',
         'builtins.complex',
         'builtins.set',
    @@ -95,7 +95,7 @@ class UnsupportedFormatErr(TypeError):
         'ipaddress.IPv4Address',
         'ipaddress.IPv6Address',
         'collections.abc.KeysView',
    -}
    +})
     
     
     TYPE_STR_TO_TYPE = {
    
  • docs/authors.rst+1 0 modified
    @@ -117,6 +117,7 @@ and polars support.
       limit when hashing numpy.datetime64
     - `Enji Cooper <https://github.com/ngie-eign>`__ for converting legacy
       setuptools use to pyproject.toml
    +- `Diogo Correia <https://github.com/diogotcorreia>`__ for reporting security vulnerability in Delta and DeepDiff that could allow remote code execution.
     
     
     .. _Sep Dehpour (Seperman): http://www.zepworks.com
    
  • docs/changelog.rst+3 0 modified
    @@ -5,6 +5,9 @@ Changelog
     
     DeepDiff Changelog
     
    +- v8-6-1
    +   - Patched security vulnerability in the Delta class which was vulnerable to class pollution via its constructor, and when combined with a gadget available in DeltaDiff itself, it could lead to Denial of Service and Remote Code Execution (via insecure Pickle deserialization).
    +
     - v8-6-0
        - Added Colored View thanks to @mauvilsa
        - Added support for applying deltas to NamedTuple thanks to @paulsc
    
  • docs/conf.py+2 2 modified
    @@ -64,9 +64,9 @@
     # built documents.
     #
     # The short X.Y version.
    -version = '8.6.0'
    +version = '8.6.1'
     # The full version, including alpha/beta/rc tags.
    -release = '8.6.0'
    +release = '8.6.1'
     
     load_dotenv(override=True)
     DOC_VERSION = os.environ.get('DOC_VERSION', version)
    
  • docs/index.rst+7 1 modified
    @@ -4,7 +4,7 @@
        contain the root `toctree` directive.
     
     
    -DeepDiff 8.6.0 documentation!
    +DeepDiff 8.6.1 documentation!
     =============================
     
     *******
    @@ -31,6 +31,12 @@ The DeepDiff library includes the following modules:
     What Is New
     ***********
     
    +DeepDiff 8-6-1
    +--------------
    +
    +    - Patched security vulnerability in the Delta class which was vulnerable to class pollution via its constructor, and when combined with a gadget available in DeltaDiff itself, it could lead to Denial of Service and Remote Code Execution (via insecure Pickle deserialization).
    +
    +
     DeepDiff 8-6-0
     --------------
     
    
  • pyproject.toml+1 1 modified
    @@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi"
     
     [project]
     name = "deepdiff"
    -version = "8.6.0"
    +version = "8.6.1"
     dependencies = [
       "orderly-set>=5.4.1,<6",
     ]
    
  • README.md+5 2 modified
    @@ -1,4 +1,4 @@
    -# DeepDiff v 8.6.0
    +# DeepDiff v 8.6.1
     
     ![Downloads](https://img.shields.io/pypi/dm/deepdiff.svg?style=flat)
     ![Python Versions](https://img.shields.io/pypi/pyversions/deepdiff.svg?style=flat)
    @@ -17,12 +17,15 @@
     
     Tested on Python 3.9+ and PyPy3.
     
    -- **[Documentation](https://zepworks.com/deepdiff/8.6.0/)**
    +- **[Documentation](https://zepworks.com/deepdiff/8.6.1/)**
     
     ## What is new?
     
     Please check the [ChangeLog](CHANGELOG.md) file for the detailed information.
     
    +DeepDiff 8-6-1
    +- Patched security vulnerability in the Delta class which was vulnerable to class pollution via its constructor, and when combined with a gadget available in DeltaDiff itself, it could lead to Denial of Service and Remote Code Execution (via insecure Pickle deserialization).
    +
     DeepDiff 8-6-0
     
     - Added Colored View thanks to @mauvilsa 
    
  • tests/test_security.py+133 0 added
    @@ -0,0 +1,133 @@
    +import os
    +import pickle
    +import pytest
    +from deepdiff import Delta
    +from deepdiff.helper import Opcode
    +from deepdiff.serialization import ForbiddenModule
    +
    +
    +class TestDeltaClassPollution:
    +
    +    def test_builtins_int(self):
    +
    +        pollute_int = pickle.dumps(
    +            {
    +                "values_changed": {"root['tmp']": {"new_value": Opcode("", 0, 0, 0, 0)}},
    +                "dictionary_item_added": {
    +                    (
    +                        ("root", "GETATTR"),
    +                        ("tmp", "GET"),
    +                        ("__repr__", "GETATTR"),
    +                        ("__globals__", "GETATTR"),
    +                        ("__builtins__", "GET"),
    +                        ("int", "GET"),
    +                    ): "no longer a class"
    +                },
    +            }
    +        )
    +
    +        assert isinstance(pollute_int, bytes)
    +
    +        # ------------[ Exploit ]------------
    +        # This could be some example, vulnerable, application.
    +        # The inputs above could be sent via HTTP, for example.
    +
    +
    +        # Existing dictionary; it is assumed that it contains
    +        # at least one entry, otherwise a different Delta needs to be
    +        # applied first, adding an entry to the dictionary.
    +        mydict = {"tmp": "foobar"}
    +
    +        # Before pollution
    +        assert 42 == int("41") + 1
    +
    +        # Apply Delta to mydict
    +        result = mydict + Delta(pollute_int)
    +
    +        assert 1337 == int("1337")
    +
    +    def test_remote_code_execution(self):
    +        if os.path.exists('/tmp/pwned'):
    +            os.remove('/tmp/pwned')
    +
    +        pollute_safe_to_import = pickle.dumps(
    +            {
    +                "values_changed": {"root['tmp']": {"new_value": Opcode("", 0, 0, 0, 0)}},
    +                "set_item_added": {
    +                    (
    +                        ("root", "GETATTR"),
    +                        ("tmp", "GET"),
    +                        ("__repr__", "GETATTR"),
    +                        ("__globals__", "GETATTR"),
    +                        ("sys", "GET"),
    +                        ("modules", "GETATTR"),
    +                        ("deepdiff.serialization", "GET"),
    +                        ("SAFE_TO_IMPORT", "GETATTR"),
    +                    ): set(["posix.system"])
    +                },
    +            }
    +        )
    +
    +        # From https://davidhamann.de/2020/04/05/exploiting-python-pickle/
    +        class RCE:
    +            def __reduce__(self):
    +                cmd = "id > /tmp/pwned"
    +                return os.system, (cmd,)
    +
    +        # Wrap object with dictionary so that Delta does not crash
    +        rce_pickle = pickle.dumps({"_": RCE()})
    +
    +        assert isinstance(pollute_safe_to_import, bytes)
    +        assert isinstance(rce_pickle, bytes)
    +
    +        # ------------[ Exploit ]------------
    +        # This could be some example, vulnerable, application.
    +        # The inputs above could be sent via HTTP, for example.
    +
    +        # Existing dictionary; it is assumed that it contains
    +        # at least one entry, otherwise a different Delta needs to be
    +        # applied first, adding an entry to the dictionary.
    +        mydict = {"tmp": "foobar"}
    +
    +        # Apply Delta to mydict
    +        with pytest.raises(ValueError) as exc_info:
    +            mydict + Delta(pollute_safe_to_import)
    +        assert "traversing dunder attributes is not allowed" == str(exc_info.value)
    +
    +        with pytest.raises(ForbiddenModule) as exc_info:
    +            Delta(rce_pickle)  # no need to apply this Delta
    +        assert "Module 'posix.system' is forbidden. You need to explicitly pass it by passing a safe_to_import parameter" == str(exc_info.value)
    +
    +        assert not os.path.exists('/tmp/pwned'), "We should not have created this file"
    +
    +    def test_delta_should_not_access_globals(self):
    +
    +        pollute_global = pickle.dumps(
    +            {
    +                "dictionary_item_added": {
    +                    (
    +                        ("root", "GETATTR"),
    +                        ("myfunc", "GETATTR"),
    +                        ("__globals__", "GETATTR"),
    +                        ("PWNED", "GET"),
    +                    ): 1337
    +                }
    +            }
    +        )
    +
    +
    +        # demo application
    +        class Foo:
    +            def __init__(self):
    +                pass
    +
    +            def myfunc(self):
    +                pass
    +
    +
    +        PWNED = False
    +        delta = Delta(pollute_global)
    +        assert PWNED is False
    +        b = Foo() + delta
    +
    +        assert PWNED is False
    
  • uv.lock+2 2 modified
    @@ -1,5 +1,5 @@
     version = 1
    -revision = 2
    +revision = 3
     requires-python = ">=3.9"
     resolution-markers = [
         "python_full_version >= '3.12'",
    @@ -273,7 +273,7 @@ wheels = [
     
     [[package]]
     name = "deepdiff"
    -version = "8.5.0"
    +version = "8.6.1"
     source = { editable = "." }
     dependencies = [
         { name = "orderly-set" },
    
60ac5b903dbd

Merge commit from fork

https://github.com/qlustered/deepdiffSep DehpourSep 3, 2025via osv
16 files changed · +178 14
  • AUTHORS.md+1 0 modified
    @@ -75,3 +75,4 @@ Authors in order of the timeline of their contributions:
     - [dtorres-sf](https://github.com/dtorres-sf) for the fix for moving nested tables when using iterable_compare_func.
     - [Jim Cipar](https://github.com/jcipar) for the fix recursion depth limit when hashing numpy.datetime64
     - [Enji Cooper](https://github.com/ngie-eign) for converting legacy setuptools use to pyproject.toml
    +- [Diogo Correia](https://github.com/diogotcorreia) for reporting security vulnerability in Delta and DeepDiff that could allow remote code execution.
    
  • .bumpversion.cfg+1 1 modified
    @@ -1,5 +1,5 @@
     [bumpversion]
    -current_version = 8.6.0
    +current_version = 8.6.1
     commit = True
     tag = True
     tag_name = {new_version}
    
  • CHANGELOG.md+4 0 modified
    @@ -1,5 +1,9 @@
     # DeepDiff Change log
     
    +- v8-6-1
    +    - Patched security vulnerability in the Delta class which was vulnerable to class pollution via its constructor, and when combined with a gadget available in DeltaDiff itself, it could lead to Denial of Service and Remote Code Execution (via insecure Pickle deserialization).
    +
    +
     - v8-6-0
         - Added Colored View thanks to @mauvilsa 
         - Added support for applying deltas to NamedTuple thanks to @paulsc 
    
  • CITATION.cff+1 1 modified
    @@ -5,6 +5,6 @@ authors:
       given-names: "Sep"
       orcid: "https://orcid.org/0009-0009-5828-4345"
     title: "DeepDiff"
    -version: 8.6.0
    +version: 8.6.1
     date-released: 2024
     url: "https://github.com/seperman/deepdiff"
    
  • deepdiff/delta.py+7 1 modified
    @@ -17,7 +17,7 @@
     )
     from deepdiff.path import (
         _path_to_elements, _get_nested_obj, _get_nested_obj_and_force,
    -    GET, GETATTR, parse_path, stringify_path,
    +    GET, GETATTR, check_elem, parse_path, stringify_path,
     )
     from deepdiff.anyset import AnySet
     from deepdiff.summarize import summarize
    @@ -237,6 +237,11 @@ def _get_elem_and_compare_to_old_value(
             forced_old_value=None,
             next_element=None,
         ):
    +        try:
    +            check_elem(elem)
    +        except ValueError as error:
    +            self._raise_or_log(UNABLE_TO_GET_ITEM_MSG.format(path_for_err_reporting, error))
    +            return not_found
             # if forced_old_value is not None:
             try:
                 if action == GET:
    @@ -536,6 +541,7 @@ def _get_elements_and_details(self, path):
                     obj = self
                     # obj = self.get_nested_obj(obj=self, elements=elements[:-1])
                 elem, action = elements[-1]  # type: ignore
    +            check_elem(elem)
             except Exception as e:
                 self._raise_or_log(UNABLE_TO_GET_ITEM_MSG.format(path, e))
                 return None
    
  • deepdiff/__init__.py+1 1 modified
    @@ -1,6 +1,6 @@
     """This module offers the DeepDiff, DeepSearch, grep, Delta and DeepHash classes."""
     # flake8: noqa
    -__version__ = '8.6.0'
    +__version__ = '8.6.1'
     import logging
     
     if __name__ == '__main__':
    
  • deepdiff/path.py+7 0 modified
    @@ -117,6 +117,7 @@ def _path_to_elements(path, root_element=DEFAULT_FIRST_ELEMENT):
     
     def _get_nested_obj(obj, elements, next_element=None):
         for (elem, action) in elements:
    +        check_elem(elem)
             if action == GET:
                 obj = obj[elem]
             elif action == GETATTR:
    @@ -134,11 +135,17 @@ def _guess_type(elements, elem, index, next_element):
         return {}
     
     
    +def check_elem(elem):
    +    if isinstance(elem, str) and elem.startswith("__") and elem.endswith("__"):
    +        raise ValueError("traversing dunder attributes is not allowed")
    +
    +
     def _get_nested_obj_and_force(obj, elements, next_element=None):
         prev_elem = None
         prev_action = None
         prev_obj = obj
         for index, (elem, action) in enumerate(elements):
    +        check_elem(elem)
             _prev_obj = obj
             if action == GET:
                 try:
    
  • deepdiff/serialization.py+2 2 modified
    @@ -59,7 +59,7 @@ class UnsupportedFormatErr(TypeError):
     DELTA_IGNORE_ORDER_NEEDS_REPETITION_REPORT = 'report_repetition must be set to True when ignore_order is True to create the delta object.'
     DELTA_ERROR_WHEN_GROUP_BY = 'Delta can not be made when group_by is used since the structure of data is modified from the original form.'
     
    -SAFE_TO_IMPORT = {
    +SAFE_TO_IMPORT = frozenset({
         'builtins.range',
         'builtins.complex',
         'builtins.set',
    @@ -95,7 +95,7 @@ class UnsupportedFormatErr(TypeError):
         'ipaddress.IPv4Address',
         'ipaddress.IPv6Address',
         'collections.abc.KeysView',
    -}
    +})
     
     
     TYPE_STR_TO_TYPE = {
    
  • docs/authors.rst+1 0 modified
    @@ -117,6 +117,7 @@ and polars support.
       limit when hashing numpy.datetime64
     - `Enji Cooper <https://github.com/ngie-eign>`__ for converting legacy
       setuptools use to pyproject.toml
    +- `Diogo Correia <https://github.com/diogotcorreia>`__ for reporting security vulnerability in Delta and DeepDiff that could allow remote code execution.
     
     
     .. _Sep Dehpour (Seperman): http://www.zepworks.com
    
  • docs/changelog.rst+3 0 modified
    @@ -5,6 +5,9 @@ Changelog
     
     DeepDiff Changelog
     
    +- v8-6-1
    +   - Patched security vulnerability in the Delta class which was vulnerable to class pollution via its constructor, and when combined with a gadget available in DeltaDiff itself, it could lead to Denial of Service and Remote Code Execution (via insecure Pickle deserialization).
    +
     - v8-6-0
        - Added Colored View thanks to @mauvilsa
        - Added support for applying deltas to NamedTuple thanks to @paulsc
    
  • docs/conf.py+2 2 modified
    @@ -64,9 +64,9 @@
     # built documents.
     #
     # The short X.Y version.
    -version = '8.6.0'
    +version = '8.6.1'
     # The full version, including alpha/beta/rc tags.
    -release = '8.6.0'
    +release = '8.6.1'
     
     load_dotenv(override=True)
     DOC_VERSION = os.environ.get('DOC_VERSION', version)
    
  • docs/index.rst+7 1 modified
    @@ -4,7 +4,7 @@
        contain the root `toctree` directive.
     
     
    -DeepDiff 8.6.0 documentation!
    +DeepDiff 8.6.1 documentation!
     =============================
     
     *******
    @@ -31,6 +31,12 @@ The DeepDiff library includes the following modules:
     What Is New
     ***********
     
    +DeepDiff 8-6-1
    +--------------
    +
    +    - Patched security vulnerability in the Delta class which was vulnerable to class pollution via its constructor, and when combined with a gadget available in DeltaDiff itself, it could lead to Denial of Service and Remote Code Execution (via insecure Pickle deserialization).
    +
    +
     DeepDiff 8-6-0
     --------------
     
    
  • pyproject.toml+1 1 modified
    @@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi"
     
     [project]
     name = "deepdiff"
    -version = "8.6.0"
    +version = "8.6.1"
     dependencies = [
       "orderly-set>=5.4.1,<6",
     ]
    
  • README.md+5 2 modified
    @@ -1,4 +1,4 @@
    -# DeepDiff v 8.6.0
    +# DeepDiff v 8.6.1
     
     ![Downloads](https://img.shields.io/pypi/dm/deepdiff.svg?style=flat)
     ![Python Versions](https://img.shields.io/pypi/pyversions/deepdiff.svg?style=flat)
    @@ -17,12 +17,15 @@
     
     Tested on Python 3.9+ and PyPy3.
     
    -- **[Documentation](https://zepworks.com/deepdiff/8.6.0/)**
    +- **[Documentation](https://zepworks.com/deepdiff/8.6.1/)**
     
     ## What is new?
     
     Please check the [ChangeLog](CHANGELOG.md) file for the detailed information.
     
    +DeepDiff 8-6-1
    +- Patched security vulnerability in the Delta class which was vulnerable to class pollution via its constructor, and when combined with a gadget available in DeltaDiff itself, it could lead to Denial of Service and Remote Code Execution (via insecure Pickle deserialization).
    +
     DeepDiff 8-6-0
     
     - Added Colored View thanks to @mauvilsa 
    
  • tests/test_security.py+133 0 added
    @@ -0,0 +1,133 @@
    +import os
    +import pickle
    +import pytest
    +from deepdiff import Delta
    +from deepdiff.helper import Opcode
    +from deepdiff.serialization import ForbiddenModule
    +
    +
    +class TestDeltaClassPollution:
    +
    +    def test_builtins_int(self):
    +
    +        pollute_int = pickle.dumps(
    +            {
    +                "values_changed": {"root['tmp']": {"new_value": Opcode("", 0, 0, 0, 0)}},
    +                "dictionary_item_added": {
    +                    (
    +                        ("root", "GETATTR"),
    +                        ("tmp", "GET"),
    +                        ("__repr__", "GETATTR"),
    +                        ("__globals__", "GETATTR"),
    +                        ("__builtins__", "GET"),
    +                        ("int", "GET"),
    +                    ): "no longer a class"
    +                },
    +            }
    +        )
    +
    +        assert isinstance(pollute_int, bytes)
    +
    +        # ------------[ Exploit ]------------
    +        # This could be some example, vulnerable, application.
    +        # The inputs above could be sent via HTTP, for example.
    +
    +
    +        # Existing dictionary; it is assumed that it contains
    +        # at least one entry, otherwise a different Delta needs to be
    +        # applied first, adding an entry to the dictionary.
    +        mydict = {"tmp": "foobar"}
    +
    +        # Before pollution
    +        assert 42 == int("41") + 1
    +
    +        # Apply Delta to mydict
    +        result = mydict + Delta(pollute_int)
    +
    +        assert 1337 == int("1337")
    +
    +    def test_remote_code_execution(self):
    +        if os.path.exists('/tmp/pwned'):
    +            os.remove('/tmp/pwned')
    +
    +        pollute_safe_to_import = pickle.dumps(
    +            {
    +                "values_changed": {"root['tmp']": {"new_value": Opcode("", 0, 0, 0, 0)}},
    +                "set_item_added": {
    +                    (
    +                        ("root", "GETATTR"),
    +                        ("tmp", "GET"),
    +                        ("__repr__", "GETATTR"),
    +                        ("__globals__", "GETATTR"),
    +                        ("sys", "GET"),
    +                        ("modules", "GETATTR"),
    +                        ("deepdiff.serialization", "GET"),
    +                        ("SAFE_TO_IMPORT", "GETATTR"),
    +                    ): set(["posix.system"])
    +                },
    +            }
    +        )
    +
    +        # From https://davidhamann.de/2020/04/05/exploiting-python-pickle/
    +        class RCE:
    +            def __reduce__(self):
    +                cmd = "id > /tmp/pwned"
    +                return os.system, (cmd,)
    +
    +        # Wrap object with dictionary so that Delta does not crash
    +        rce_pickle = pickle.dumps({"_": RCE()})
    +
    +        assert isinstance(pollute_safe_to_import, bytes)
    +        assert isinstance(rce_pickle, bytes)
    +
    +        # ------------[ Exploit ]------------
    +        # This could be some example, vulnerable, application.
    +        # The inputs above could be sent via HTTP, for example.
    +
    +        # Existing dictionary; it is assumed that it contains
    +        # at least one entry, otherwise a different Delta needs to be
    +        # applied first, adding an entry to the dictionary.
    +        mydict = {"tmp": "foobar"}
    +
    +        # Apply Delta to mydict
    +        with pytest.raises(ValueError) as exc_info:
    +            mydict + Delta(pollute_safe_to_import)
    +        assert "traversing dunder attributes is not allowed" == str(exc_info.value)
    +
    +        with pytest.raises(ForbiddenModule) as exc_info:
    +            Delta(rce_pickle)  # no need to apply this Delta
    +        assert "Module 'posix.system' is forbidden. You need to explicitly pass it by passing a safe_to_import parameter" == str(exc_info.value)
    +
    +        assert not os.path.exists('/tmp/pwned'), "We should not have created this file"
    +
    +    def test_delta_should_not_access_globals(self):
    +
    +        pollute_global = pickle.dumps(
    +            {
    +                "dictionary_item_added": {
    +                    (
    +                        ("root", "GETATTR"),
    +                        ("myfunc", "GETATTR"),
    +                        ("__globals__", "GETATTR"),
    +                        ("PWNED", "GET"),
    +                    ): 1337
    +                }
    +            }
    +        )
    +
    +
    +        # demo application
    +        class Foo:
    +            def __init__(self):
    +                pass
    +
    +            def myfunc(self):
    +                pass
    +
    +
    +        PWNED = False
    +        delta = Delta(pollute_global)
    +        assert PWNED is False
    +        b = Foo() + delta
    +
    +        assert PWNED is False
    
  • uv.lock+2 2 modified
    @@ -1,5 +1,5 @@
     version = 1
    -revision = 2
    +revision = 3
     requires-python = ">=3.9"
     resolution-markers = [
         "python_full_version >= '3.12'",
    @@ -273,7 +273,7 @@ wheels = [
     
     [[package]]
     name = "deepdiff"
    -version = "8.5.0"
    +version = "8.6.1"
     source = { editable = "." }
     dependencies = [
         { name = "orderly-set" },
    
c69c06c13f75

Security fix: Prevent class pollution and remote code execution in Delta

https://github.com/qlustered/deepdiffSep DehpourSep 3, 2025via osv
4 files changed · +149 3
  • deepdiff/delta.py+7 1 modified
    @@ -17,7 +17,7 @@
     )
     from deepdiff.path import (
         _path_to_elements, _get_nested_obj, _get_nested_obj_and_force,
    -    GET, GETATTR, parse_path, stringify_path,
    +    GET, GETATTR, check_elem, parse_path, stringify_path,
     )
     from deepdiff.anyset import AnySet
     from deepdiff.summarize import summarize
    @@ -237,6 +237,11 @@ def _get_elem_and_compare_to_old_value(
             forced_old_value=None,
             next_element=None,
         ):
    +        try:
    +            check_elem(elem)
    +        except ValueError as error:
    +            self._raise_or_log(UNABLE_TO_GET_ITEM_MSG.format(path_for_err_reporting, error))
    +            return not_found
             # if forced_old_value is not None:
             try:
                 if action == GET:
    @@ -536,6 +541,7 @@ def _get_elements_and_details(self, path):
                     obj = self
                     # obj = self.get_nested_obj(obj=self, elements=elements[:-1])
                 elem, action = elements[-1]  # type: ignore
    +            check_elem(elem)
             except Exception as e:
                 self._raise_or_log(UNABLE_TO_GET_ITEM_MSG.format(path, e))
                 return None
    
  • deepdiff/path.py+7 0 modified
    @@ -117,6 +117,7 @@ def _path_to_elements(path, root_element=DEFAULT_FIRST_ELEMENT):
     
     def _get_nested_obj(obj, elements, next_element=None):
         for (elem, action) in elements:
    +        check_elem(elem)
             if action == GET:
                 obj = obj[elem]
             elif action == GETATTR:
    @@ -134,11 +135,17 @@ def _guess_type(elements, elem, index, next_element):
         return {}
     
     
    +def check_elem(elem):
    +    if isinstance(elem, str) and elem.startswith("__") and elem.endswith("__"):
    +        raise ValueError("traversing dunder attributes is not allowed")
    +
    +
     def _get_nested_obj_and_force(obj, elements, next_element=None):
         prev_elem = None
         prev_action = None
         prev_obj = obj
         for index, (elem, action) in enumerate(elements):
    +        check_elem(elem)
             _prev_obj = obj
             if action == GET:
                 try:
    
  • deepdiff/serialization.py+2 2 modified
    @@ -59,7 +59,7 @@ class UnsupportedFormatErr(TypeError):
     DELTA_IGNORE_ORDER_NEEDS_REPETITION_REPORT = 'report_repetition must be set to True when ignore_order is True to create the delta object.'
     DELTA_ERROR_WHEN_GROUP_BY = 'Delta can not be made when group_by is used since the structure of data is modified from the original form.'
     
    -SAFE_TO_IMPORT = {
    +SAFE_TO_IMPORT = frozenset({
         'builtins.range',
         'builtins.complex',
         'builtins.set',
    @@ -95,7 +95,7 @@ class UnsupportedFormatErr(TypeError):
         'ipaddress.IPv4Address',
         'ipaddress.IPv6Address',
         'collections.abc.KeysView',
    -}
    +})
     
     
     TYPE_STR_TO_TYPE = {
    
  • tests/test_security.py+133 0 added
    @@ -0,0 +1,133 @@
    +import os
    +import pickle
    +import pytest
    +from deepdiff import Delta
    +from deepdiff.helper import Opcode
    +from deepdiff.serialization import ForbiddenModule
    +
    +
    +class TestDeltaClassPollution:
    +
    +    def test_builtins_int(self):
    +
    +        pollute_int = pickle.dumps(
    +            {
    +                "values_changed": {"root['tmp']": {"new_value": Opcode("", 0, 0, 0, 0)}},
    +                "dictionary_item_added": {
    +                    (
    +                        ("root", "GETATTR"),
    +                        ("tmp", "GET"),
    +                        ("__repr__", "GETATTR"),
    +                        ("__globals__", "GETATTR"),
    +                        ("__builtins__", "GET"),
    +                        ("int", "GET"),
    +                    ): "no longer a class"
    +                },
    +            }
    +        )
    +
    +        assert isinstance(pollute_int, bytes)
    +
    +        # ------------[ Exploit ]------------
    +        # This could be some example, vulnerable, application.
    +        # The inputs above could be sent via HTTP, for example.
    +
    +
    +        # Existing dictionary; it is assumed that it contains
    +        # at least one entry, otherwise a different Delta needs to be
    +        # applied first, adding an entry to the dictionary.
    +        mydict = {"tmp": "foobar"}
    +
    +        # Before pollution
    +        assert 42 == int("41") + 1
    +
    +        # Apply Delta to mydict
    +        result = mydict + Delta(pollute_int)
    +
    +        assert 1337 == int("1337")
    +
    +    def test_remote_code_execution(self):
    +        if os.path.exists('/tmp/pwned'):
    +            os.remove('/tmp/pwned')
    +
    +        pollute_safe_to_import = pickle.dumps(
    +            {
    +                "values_changed": {"root['tmp']": {"new_value": Opcode("", 0, 0, 0, 0)}},
    +                "set_item_added": {
    +                    (
    +                        ("root", "GETATTR"),
    +                        ("tmp", "GET"),
    +                        ("__repr__", "GETATTR"),
    +                        ("__globals__", "GETATTR"),
    +                        ("sys", "GET"),
    +                        ("modules", "GETATTR"),
    +                        ("deepdiff.serialization", "GET"),
    +                        ("SAFE_TO_IMPORT", "GETATTR"),
    +                    ): set(["posix.system"])
    +                },
    +            }
    +        )
    +
    +        # From https://davidhamann.de/2020/04/05/exploiting-python-pickle/
    +        class RCE:
    +            def __reduce__(self):
    +                cmd = "id > /tmp/pwned"
    +                return os.system, (cmd,)
    +
    +        # Wrap object with dictionary so that Delta does not crash
    +        rce_pickle = pickle.dumps({"_": RCE()})
    +
    +        assert isinstance(pollute_safe_to_import, bytes)
    +        assert isinstance(rce_pickle, bytes)
    +
    +        # ------------[ Exploit ]------------
    +        # This could be some example, vulnerable, application.
    +        # The inputs above could be sent via HTTP, for example.
    +
    +        # Existing dictionary; it is assumed that it contains
    +        # at least one entry, otherwise a different Delta needs to be
    +        # applied first, adding an entry to the dictionary.
    +        mydict = {"tmp": "foobar"}
    +
    +        # Apply Delta to mydict
    +        with pytest.raises(ValueError) as exc_info:
    +            mydict + Delta(pollute_safe_to_import)
    +        assert "traversing dunder attributes is not allowed" == str(exc_info.value)
    +
    +        with pytest.raises(ForbiddenModule) as exc_info:
    +            Delta(rce_pickle)  # no need to apply this Delta
    +        assert "Module 'posix.system' is forbidden. You need to explicitly pass it by passing a safe_to_import parameter" == str(exc_info.value)
    +
    +        assert not os.path.exists('/tmp/pwned'), "We should not have created this file"
    +
    +    def test_delta_should_not_access_globals(self):
    +
    +        pollute_global = pickle.dumps(
    +            {
    +                "dictionary_item_added": {
    +                    (
    +                        ("root", "GETATTR"),
    +                        ("myfunc", "GETATTR"),
    +                        ("__globals__", "GETATTR"),
    +                        ("PWNED", "GET"),
    +                    ): 1337
    +                }
    +            }
    +        )
    +
    +
    +        # demo application
    +        class Foo:
    +            def __init__(self):
    +                pass
    +
    +            def myfunc(self):
    +                pass
    +
    +
    +        PWNED = False
    +        delta = Delta(pollute_global)
    +        assert PWNED is False
    +        b = Foo() + delta
    +
    +        assert PWNED is False
    
c69c06c13f75

Security fix: Prevent class pollution and remote code execution in Delta

https://github.com/seperman/deepdiffSep DehpourSep 3, 2025via ghsa
4 files changed · +149 3
  • deepdiff/delta.py+7 1 modified
    @@ -17,7 +17,7 @@
     )
     from deepdiff.path import (
         _path_to_elements, _get_nested_obj, _get_nested_obj_and_force,
    -    GET, GETATTR, parse_path, stringify_path,
    +    GET, GETATTR, check_elem, parse_path, stringify_path,
     )
     from deepdiff.anyset import AnySet
     from deepdiff.summarize import summarize
    @@ -237,6 +237,11 @@ def _get_elem_and_compare_to_old_value(
             forced_old_value=None,
             next_element=None,
         ):
    +        try:
    +            check_elem(elem)
    +        except ValueError as error:
    +            self._raise_or_log(UNABLE_TO_GET_ITEM_MSG.format(path_for_err_reporting, error))
    +            return not_found
             # if forced_old_value is not None:
             try:
                 if action == GET:
    @@ -536,6 +541,7 @@ def _get_elements_and_details(self, path):
                     obj = self
                     # obj = self.get_nested_obj(obj=self, elements=elements[:-1])
                 elem, action = elements[-1]  # type: ignore
    +            check_elem(elem)
             except Exception as e:
                 self._raise_or_log(UNABLE_TO_GET_ITEM_MSG.format(path, e))
                 return None
    
  • deepdiff/path.py+7 0 modified
    @@ -117,6 +117,7 @@ def _path_to_elements(path, root_element=DEFAULT_FIRST_ELEMENT):
     
     def _get_nested_obj(obj, elements, next_element=None):
         for (elem, action) in elements:
    +        check_elem(elem)
             if action == GET:
                 obj = obj[elem]
             elif action == GETATTR:
    @@ -134,11 +135,17 @@ def _guess_type(elements, elem, index, next_element):
         return {}
     
     
    +def check_elem(elem):
    +    if isinstance(elem, str) and elem.startswith("__") and elem.endswith("__"):
    +        raise ValueError("traversing dunder attributes is not allowed")
    +
    +
     def _get_nested_obj_and_force(obj, elements, next_element=None):
         prev_elem = None
         prev_action = None
         prev_obj = obj
         for index, (elem, action) in enumerate(elements):
    +        check_elem(elem)
             _prev_obj = obj
             if action == GET:
                 try:
    
  • deepdiff/serialization.py+2 2 modified
    @@ -59,7 +59,7 @@ class UnsupportedFormatErr(TypeError):
     DELTA_IGNORE_ORDER_NEEDS_REPETITION_REPORT = 'report_repetition must be set to True when ignore_order is True to create the delta object.'
     DELTA_ERROR_WHEN_GROUP_BY = 'Delta can not be made when group_by is used since the structure of data is modified from the original form.'
     
    -SAFE_TO_IMPORT = {
    +SAFE_TO_IMPORT = frozenset({
         'builtins.range',
         'builtins.complex',
         'builtins.set',
    @@ -95,7 +95,7 @@ class UnsupportedFormatErr(TypeError):
         'ipaddress.IPv4Address',
         'ipaddress.IPv6Address',
         'collections.abc.KeysView',
    -}
    +})
     
     
     TYPE_STR_TO_TYPE = {
    
  • tests/test_security.py+133 0 added
    @@ -0,0 +1,133 @@
    +import os
    +import pickle
    +import pytest
    +from deepdiff import Delta
    +from deepdiff.helper import Opcode
    +from deepdiff.serialization import ForbiddenModule
    +
    +
    +class TestDeltaClassPollution:
    +
    +    def test_builtins_int(self):
    +
    +        pollute_int = pickle.dumps(
    +            {
    +                "values_changed": {"root['tmp']": {"new_value": Opcode("", 0, 0, 0, 0)}},
    +                "dictionary_item_added": {
    +                    (
    +                        ("root", "GETATTR"),
    +                        ("tmp", "GET"),
    +                        ("__repr__", "GETATTR"),
    +                        ("__globals__", "GETATTR"),
    +                        ("__builtins__", "GET"),
    +                        ("int", "GET"),
    +                    ): "no longer a class"
    +                },
    +            }
    +        )
    +
    +        assert isinstance(pollute_int, bytes)
    +
    +        # ------------[ Exploit ]------------
    +        # This could be some example, vulnerable, application.
    +        # The inputs above could be sent via HTTP, for example.
    +
    +
    +        # Existing dictionary; it is assumed that it contains
    +        # at least one entry, otherwise a different Delta needs to be
    +        # applied first, adding an entry to the dictionary.
    +        mydict = {"tmp": "foobar"}
    +
    +        # Before pollution
    +        assert 42 == int("41") + 1
    +
    +        # Apply Delta to mydict
    +        result = mydict + Delta(pollute_int)
    +
    +        assert 1337 == int("1337")
    +
    +    def test_remote_code_execution(self):
    +        if os.path.exists('/tmp/pwned'):
    +            os.remove('/tmp/pwned')
    +
    +        pollute_safe_to_import = pickle.dumps(
    +            {
    +                "values_changed": {"root['tmp']": {"new_value": Opcode("", 0, 0, 0, 0)}},
    +                "set_item_added": {
    +                    (
    +                        ("root", "GETATTR"),
    +                        ("tmp", "GET"),
    +                        ("__repr__", "GETATTR"),
    +                        ("__globals__", "GETATTR"),
    +                        ("sys", "GET"),
    +                        ("modules", "GETATTR"),
    +                        ("deepdiff.serialization", "GET"),
    +                        ("SAFE_TO_IMPORT", "GETATTR"),
    +                    ): set(["posix.system"])
    +                },
    +            }
    +        )
    +
    +        # From https://davidhamann.de/2020/04/05/exploiting-python-pickle/
    +        class RCE:
    +            def __reduce__(self):
    +                cmd = "id > /tmp/pwned"
    +                return os.system, (cmd,)
    +
    +        # Wrap object with dictionary so that Delta does not crash
    +        rce_pickle = pickle.dumps({"_": RCE()})
    +
    +        assert isinstance(pollute_safe_to_import, bytes)
    +        assert isinstance(rce_pickle, bytes)
    +
    +        # ------------[ Exploit ]------------
    +        # This could be some example, vulnerable, application.
    +        # The inputs above could be sent via HTTP, for example.
    +
    +        # Existing dictionary; it is assumed that it contains
    +        # at least one entry, otherwise a different Delta needs to be
    +        # applied first, adding an entry to the dictionary.
    +        mydict = {"tmp": "foobar"}
    +
    +        # Apply Delta to mydict
    +        with pytest.raises(ValueError) as exc_info:
    +            mydict + Delta(pollute_safe_to_import)
    +        assert "traversing dunder attributes is not allowed" == str(exc_info.value)
    +
    +        with pytest.raises(ForbiddenModule) as exc_info:
    +            Delta(rce_pickle)  # no need to apply this Delta
    +        assert "Module 'posix.system' is forbidden. You need to explicitly pass it by passing a safe_to_import parameter" == str(exc_info.value)
    +
    +        assert not os.path.exists('/tmp/pwned'), "We should not have created this file"
    +
    +    def test_delta_should_not_access_globals(self):
    +
    +        pollute_global = pickle.dumps(
    +            {
    +                "dictionary_item_added": {
    +                    (
    +                        ("root", "GETATTR"),
    +                        ("myfunc", "GETATTR"),
    +                        ("__globals__", "GETATTR"),
    +                        ("PWNED", "GET"),
    +                    ): 1337
    +                }
    +            }
    +        )
    +
    +
    +        # demo application
    +        class Foo:
    +            def __init__(self):
    +                pass
    +
    +            def myfunc(self):
    +                pass
    +
    +
    +        PWNED = False
    +        delta = Delta(pollute_global)
    +        assert PWNED is False
    +        b = Foo() + delta
    +
    +        assert PWNED is False
    
2015f0a4bcdb

fix: only prevent access to object paths containing __globals__ and __builtins__ for non-dict/list objects

https://github.com/dgilland/pydashDerrick GillandFeb 24, 2023via ghsa
3 files changed · +28 10
  • CHANGELOG.rst+3 0 modified
    @@ -3,6 +3,9 @@
     Changelog
     =========
     
    +- Only prevent access to object paths containing ``__globals__`` or ``__builtins__`` instead of all dunder-methods for non-dict/list objects.
    +
    +
     v6.0.1 (2023-02-20)
     -------------------
     
    
  • src/pydash/helpers.py+5 5 modified
    @@ -20,6 +20,9 @@
     #: Dictionary of builtins with keys as the builtin function and values as the string name.
     BUILTINS = {value: key for key, value in builtins.__dict__.items() if isinstance(value, Hashable)}
     
    +#: Object keys that are restricted from access via path access.
    +RESTRICTED_KEYS = ("__globals__", "__builtins__")
    +
     
     def callit(iteratee, *args, **kwargs):
         """Inspect argspec of `iteratee` function and only pass the supported arguments when calling
    @@ -188,11 +191,8 @@ def _base_get_object(obj, key, default=UNSET):
     
     
     def _raise_if_restricted_key(key):
    -    if not isinstance(key, str):
    -        return
    -    # Prevent access to dunder-methods since this could expose access to globals through leaky
    -    # attributes such as obj.__init__.__globals__.
    -    if len(key) > 4 and key.isascii() and key.startswith("__") and key.endswith("__"):
    +    # Prevent access to restricted keys for security reasons.
    +    if key in RESTRICTED_KEYS:
             raise KeyError(f"access to restricted key {key!r} is not allowed")
     
     
    
  • tests/test_objects.py+20 5 modified
    @@ -384,9 +384,10 @@ def test_get__should_not_populate_defaultdict():
     @parametrize(
         "obj,path",
         [
    -        (helpers.Object(), "__init__"),
    -        (helpers.Object(subobj=helpers.Object()), "subobj.__init__"),
    -        (namedtuple("a", ["a"])(a=1), "__len__"),
    +        (helpers.Object(), "__init__.__globals__"),
    +        (namedtuple("a", ["a"])(a=1), "__globals__"),
    +        (helpers.Object(subobj=helpers.Object()), "subobj.__builtins__"),
    +        (helpers.Object(subobj=helpers.Object()), "__builtins__"),
         ],
     )
     def test_get__raises_for_objects_when_path_restricted(obj, path):
    @@ -397,14 +398,28 @@ def test_get__raises_for_objects_when_path_restricted(obj, path):
     @parametrize(
         "obj,path",
         [
    -        ({}, "__init__"),
    -        ([], "__contains__"),
    +        ({}, "__globals__"),
    +        ({}, "__builtins__"),
    +        ([], "__globals__"),
    +        ([], "__builtins__"),
         ],
     )
     def test_get__does_not_raise_for_dict_or_list_when_path_restricted(obj, path):
         assert _.get(obj, path) is None
     
     
    +@parametrize(
    +    "obj,path",
    +    [
    +        (helpers.Object(), "__name__"),
    +        (helpers.Object(), "foo.__dict__"),
    +        (helpers.Object(), "__len__"),
    +    ],
    +)
    +def test_get__does_not_raise_for_objects_when_path_is_unrestricted(obj, path):
    +    assert _.get(obj, path) is None
    +
    +
     @parametrize(
         "case,expected",
         [
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.