SQL injection / Query construction safety

HIGH
django/django
Commit: 005d60d97c4d
Affected: 5.1.x series prior to this commit (stable/5.1.x); CVE-2026-1312 fix
2026-04-05 13:51 UTC

Description

The commit adds input validation to FilteredRelation alias handling to reject any alias containing a period. Previously, an alias like 'book.alice' could be used in annotations, leading to unquoted identifiers in the SQL ON clause and potentially invalid or insecure query construction. The patch raises a ValueError when a dot is present in the alias, preventing problematic SQL generation. Tests were added to enforce this behavior (test_period_forbidden) and to ensure ordering tests expect a ValueError rather than a DatabaseError for such aliases.

Proof of Concept

# PoC to reproduce before the fix (and after the fix raises an error) # Prerequisites: Django project with models similar to the tests # models: class Book(models.Model): title = models.CharField(...); class Author(models.Model): book = models.ForeignKey(Book, on_delete=CASCADE) from django.db.models import FilteredRelation, Q # Attempt to annotate with a dot-containing alias Author.objects.annotate(**{ "book.alice": FilteredRelation( "book", condition=Q(book__title__iexact="poem by alice") ) }) # Expected before the fix: could raise a DatabaseError due to invalid SQL; with the fix, a clear ValueError is raised: # ValueError: FilteredRelation doesn't support aliases with periods (got 'book.alice').

Commit Details

Author: Jacob Walls

Date: 2026-01-21 23:00 UTC

Message:

Refs CVE-2026-1312 -- Raised ValueError when FilteredRelation aliases contain periods. This prevents failures at the database layer, given that aliases in the ON clause are not quoted. Systematically quoting aliases even in FilteredRelation is tracked in https://code.djangoproject.com/ticket/36795.

Triage Assessment

Vulnerability Type: SQL injection / Query construction safety

Confidence: HIGH

Reasoning:

The commit explicitly references a CVE and adds input validation to prevent aliases containing periods in FilteredRelation, raising a ValueError. This guards against problematic SQL generation in the ON clause due to unquoted aliases, which is a security-sensitive area in query construction.

Verification Assessment

Vulnerability Type: SQL injection / Query construction safety

Confidence: HIGH

Affected Versions: 5.1.x series prior to this commit (stable/5.1.x); CVE-2026-1312 fix

Code Diff

diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index c6f080dcbb3c..7a4cf843c1b1 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -1720,6 +1720,11 @@ def _add_q( return target_clause, needed_inner def add_filtered_relation(self, filtered_relation, alias): + if "." in alias: + raise ValueError( + "FilteredRelation doesn't support aliases with periods " + "(got %r)." % alias + ) self.check_alias(alias) filtered_relation.alias = alias relation_lookup_parts, relation_field_parts, _ = self.solve_lookup_type( diff --git a/tests/filtered_relation/tests.py b/tests/filtered_relation/tests.py index cdcd5c19afa8..e263307193cc 100644 --- a/tests/filtered_relation/tests.py +++ b/tests/filtered_relation/tests.py @@ -216,6 +216,19 @@ def test_internal_queryset_alias_mapping(self): str(queryset.query), ) + def test_period_forbidden(self): + msg = ( + "FilteredRelation doesn't support aliases with periods (got 'book.alice')." + ) + with self.assertRaisesMessage(ValueError, msg): + Author.objects.annotate( + **{ + "book.alice": FilteredRelation( + "book", condition=Q(book__title__iexact="poem by alice") + ) + } + ) + def test_multiple(self): qs = ( Author.objects.annotate( diff --git a/tests/ordering/tests.py b/tests/ordering/tests.py index afe2e3c22cc6..008f0239b319 100644 --- a/tests/ordering/tests.py +++ b/tests/ordering/tests.py @@ -18,7 +18,6 @@ When, ) from django.db.models.functions import Length, Upper -from django.db.utils import DatabaseError from django.test import TestCase from .models import ( @@ -411,13 +410,19 @@ def test_alias_with_period_shadows_table_name(self): self.assertNotEqual(qs[0].headline, "Backdated") relation = FilteredRelation("author") - qs2 = Article.objects.annotate(**{crafted: relation}).order_by(crafted) - with self.assertRaises(DatabaseError): + msg = ( + "FilteredRelation doesn't support aliases with periods " + "(got 'ordering_article.pub_date')." + ) + with self.assertRaisesMessage(ValueError, msg): + qs2 = Article.objects.annotate(**{crafted: relation}).order_by(crafted) # Before, unlike F(), which causes ordering expressions to be # replaced by ordinals like n in ORDER BY n, these were ordered by # pub_date instead of author. # The Article model orders by -pk, so sorting on author will place # first any article by author2 instead of the backdated one. + # This assertion is reachable if FilteredRelation.__init__() starts + # supporting periods in aliases in the future. self.assertNotEqual(qs2[0].headline, "Backdated") def test_order_by_pk(self):
← Back to Alerts View on GitHub →