Security bypass / information disclosure via ORM query construction

HIGH
django/django
Commit: 3c3f46357718
Affected: 5.1.x (pre-fix)
2026-04-05 13:21 UTC

Description

The commit adds validation to prevent certain internal kwargs (_connector, _negated) from being propagated when expanding dictionaries into Q objects during ORM query construction. This blocks the ability to inject or influence Q semantics via dictionary expansion in filter/exclude calls, which could otherwise enable security bypass or information disclosure by altering the intended query behavior. The change introduces PROHIBITED_FILTER_KWARGS and raises a TypeError if any of these keys are present in the kwargs, plus a test verifying the rejection.

Commit Details

Author: Jacob Walls

Date: 2025-09-24 19:56 UTC

Message:

Refs CVE-2025-64459 -- Avoided propagating invalid arguments to Q on dictionary expansion.

Triage Assessment

Vulnerability Type: Security bypass / information disclosure via ORM query construction

Confidence: HIGH

Reasoning:

The commit adds validation to prevent certain internal kwargs (_connector, _negated) from being carried through when expanding dictionaries into Q objects. This blocks potential abuse or bypasses in query construction (validated against the CVE reference), reducing the risk of unintended query behavior that could lead to information disclosure or authorization bypass.

Verification Assessment

Vulnerability Type: Security bypass / information disclosure via ORM query construction

Confidence: HIGH

Affected Versions: 5.1.x (pre-fix)

Code Diff

diff --git a/django/db/models/query.py b/django/db/models/query.py index 70177667a606..3c9c42cdc774 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -46,6 +46,8 @@ # The maximum number of items to display in a QuerySet.__repr__ REPR_OUTPUT_SIZE = 20 +PROHIBITED_FILTER_KWARGS = frozenset(["_connector", "_negated"]) + class BaseIterable: def __init__( @@ -1645,6 +1647,9 @@ def _filter_or_exclude(self, negate, args, kwargs): return clone def _filter_or_exclude_inplace(self, negate, args, kwargs): + if invalid_kwargs := PROHIBITED_FILTER_KWARGS.intersection(kwargs): + invalid_kwargs_str = ", ".join(f"'{k}'" for k in sorted(invalid_kwargs)) + raise TypeError(f"The following kwargs are invalid: {invalid_kwargs_str}") if negate: self._query.add_q(~Q(*args, **kwargs)) else: diff --git a/tests/queries/tests.py b/tests/queries/tests.py index 4ee457271904..51d1915c97c5 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -4516,6 +4516,14 @@ def test_invalid_values(self): Annotation.objects.filter(tag__in=[123, "abc"]) +class TestInvalidFilterArguments(TestCase): + def test_filter_rejects_invalid_arguments(self): + school = School.objects.create() + msg = "The following kwargs are invalid: '_connector', '_negated'" + with self.assertRaisesMessage(TypeError, msg): + School.objects.filter(pk=school.pk, _negated=True, _connector="evil") + + class TestTicket24605(TestCase): def test_ticket_24605(self): """
← Back to Alerts View on GitHub →