VYPR
Medium severity5.3OSV Advisory· Published Dec 22, 2025· Updated Apr 15, 2026

CVE-2025-68480

CVE-2025-68480

Description

Marshmallow is a lightweight library for converting complex objects to and from simple Python datatypes. In versions from 3.0.0rc1 to before 3.26.2 and from 4.0.0 to before 4.1.2, Schema.load(data, many=True) is vulnerable to denial of service attacks. A moderately sized request can consume a disproportionate amount of CPU time. This issue has been patched in version 3.26.2 and 4.1.2.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
marshmallowPyPI
>= 3.0.0rc1, < 3.26.23.26.2
marshmallowPyPI
>= 4.0.0, < 4.1.24.1.2

Affected products

1

Patches

1
d24a0c9df061

Merge commit from fork

https://github.com/marshmallow-code/marshmallowJared DeckardDec 22, 2025via ghsa
4 files changed · +47 14
  • CHANGELOG.rst+8 0 modified
    @@ -1,6 +1,14 @@
     Changelog
     ---------
     
    +4.1.2 (2025-12-19)
    +++++++++++++++++++
    +
    +Bug fixes:
    +
    +- :cve:`CVE-2025-68480`: Merge error store messages without rebuilding collections.
    +  Thanks 카푸치노 for reporting and :user:`deckar01` for the fix.
    +
     4.1.1 (2025-11-05)
     ++++++++++++++++++
     
    
  • pyproject.toml+1 1 modified
    @@ -1,6 +1,6 @@
     [project]
     name = "marshmallow"
    -version = "4.1.1"
    +version = "4.1.2"
     description = "A lightweight library for converting complex datatypes to and from native Python datatypes."
     readme = "README.rst"
     license = { file = "LICENSE" }
    
  • src/marshmallow/error_store.py+21 12 modified
    @@ -18,12 +18,19 @@ def store_error(self, messages, field_name=SCHEMA, index=None):
             # field error  -> store/merge error messages under field name key
             # schema error -> if string or list, store/merge under _schema key
             #              -> if dict, store/merge with other top-level keys
    +        messages = copy_containers(messages)
             if field_name != SCHEMA or not isinstance(messages, dict):
                 messages = {field_name: messages}
             if index is not None:
                 messages = {index: messages}
             self.errors = merge_errors(self.errors, messages)
     
    +def copy_containers(errors):
    +    if isinstance(errors, list):
    +        return [copy_containers(val) for val in errors]
    +    if isinstance(errors, dict):
    +        return {key: copy_containers(val) for key, val in errors.items()}
    +    return errors
     
     def merge_errors(errors1, errors2):  # noqa: PLR0911
         """Deeply merge two error messages.
    @@ -37,24 +44,26 @@ def merge_errors(errors1, errors2):  # noqa: PLR0911
             return errors1
         if isinstance(errors1, list):
             if isinstance(errors2, list):
    -            return errors1 + errors2
    +            errors1.extend(errors2)
    +            return errors1
             if isinstance(errors2, dict):
    -            return dict(errors2, **{SCHEMA: merge_errors(errors1, errors2.get(SCHEMA))})
    -        return [*errors1, errors2]
    +            errors2[SCHEMA] = merge_errors(errors1, errors2.get(SCHEMA))
    +            return errors2
    +        errors1.append(errors2)
    +        return errors1
         if isinstance(errors1, dict):
    -        if isinstance(errors2, list):
    -            return dict(errors1, **{SCHEMA: merge_errors(errors1.get(SCHEMA), errors2)})
             if isinstance(errors2, dict):
    -            errors = dict(errors1)
                 for key, val in errors2.items():
    -                if key in errors:
    -                    errors[key] = merge_errors(errors[key], val)
    +                if key in errors1:
    +                    errors1[key] = merge_errors(errors1[key], val)
                     else:
    -                    errors[key] = val
    -            return errors
    -        return dict(errors1, **{SCHEMA: merge_errors(errors1.get(SCHEMA), errors2)})
    +                    errors1[key] = val
    +            return errors1
    +        errors1[SCHEMA] = merge_errors(errors1.get(SCHEMA), errors2)
    +        return errors1
         if isinstance(errors2, list):
             return [errors1, *errors2]
         if isinstance(errors2, dict):
    -        return dict(errors2, **{SCHEMA: merge_errors(errors1, errors2.get(SCHEMA))})
    +        errors2[SCHEMA] = merge_errors(errors1, errors2.get(SCHEMA))
    +        return errors2
         return [errors1, errors2]
    
  • tests/test_error_store.py+17 1 modified
    @@ -1,7 +1,7 @@
     from typing import NamedTuple
     
     from marshmallow import missing
    -from marshmallow.error_store import merge_errors
    +from marshmallow.error_store import merge_errors, ErrorStore
     
     
     def test_missing_is_falsy():
    @@ -149,3 +149,19 @@ def test_deep_merging_dicts(self):
             assert merge_errors(
                 {"field1": {"field2": "error1"}}, {"field1": {"field2": "error2"}}
             ) == {"field1": {"field2": ["error1", "error2"]}}
    +
    +    def test_list_not_changed(self):
    +        store = ErrorStore()
    +        message = ["foo"]
    +        store.store_error(message)
    +        store.store_error(message)
    +        assert message == ["foo"]
    +        assert store.errors == {"_schema": ["foo", "foo"]}
    +
    +    def test_dict_not_changed(self):
    +        store = ErrorStore()
    +        message = {"foo": ["bar"]}
    +        store.store_error(message)
    +        store.store_error(message)
    +        assert message == {"foo": ["bar"]}
    +        assert store.errors == {"foo": ["bar", "bar"]}
    

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

4

News mentions

0

No linked articles in our index yet.