# Django — CWE-407 Disclosure Brief **2026-03-27 · Patch available — awaiting upstream merge** ## Finding Four O(n²) defects in Django's ORM queryset layer, serializer, system check framework, and raw SQL resolution. All patched. Patches ready for upstream review. ## The Defects **django-0001 (PATCHED — HIGH):** `db/models/base.py:622` ```python # Inside Model.from_db() — called per row in ModelIterable.__iter__: next(values_iter) if f.attname in field_names else DEFERRED ``` `field_names` is a plain Python list. `f.attname in field_names` is O(F) per field, called once per concrete field per row. Total: O(N × F²) where N = row count, F = field count. Irony: `.defer()` and `.only()` are Django's recommended performance optimization patterns — the optimization path has quadratic overhead baked in. **django-0002 (PATCHED — HIGH):** `core/serializers/base.py:130,136,143` ```python # Three membership tests per field per object in Serializer.serialize(): if field.attname in self.selected_fields: # × 3 per field ``` `self.selected_fields` is stored as a plain list — three O(S) scans per field per serialized object. O(N × F × S) total. Triggered by `dumpdata`, `loaddata`, REST serialization, and Django REST Framework. **django-0003 (PATCHED — MEDIUM):** `db/models/base.py:2081` ```python # In _check_column_name_clashes(): if column_name in used_column_names: # O(F) per field ... used_column_names.append(column_name) ``` `used_column_names` is a list. O(F²) total. Runs at startup and on `manage.py check` for every model class. **django-0004 (PATCHED — MEDIUM):** `db/models/query.py:2381,2389` ```python # In RawQuerySet.resolve_model_init_order(): if column_name in self.columns: # O(C) list scan ... self.columns.index(f.column) # O(C) list scan ``` Two separate O(C) list scans per field. `self.columns` is a plain list. ## Complexity Proof **django-0001:** For N rows and F concrete fields per model: - Per row: F membership checks, each O(F) - Total: **O(N × F²)** At N=1,000 rows, F=50 fields: defective=2,500,000 comparisons, fixed=50,000. **50× op reduction.** **django-0002:** For N objects, F fields, S selected fields: - Three scans per field per object: **O(N × F × S)** **Measured ratio: 10× at N=500, F=50, S=50.** **django-0003:** For F fields per model: - Each field checks membership in a growing list: **O(F²)** **Measured ratio: 125× at F=500.** **django-0004:** Two O(C) list scans per field: **O(F × C).** **Measured ratio: 101× at F=C=500.** ## Impact django-0001 fires on every `.defer()` or `.only()` queryset iteration — the standard Django pattern for large-table performance optimization. Every Django site using deferred loading pays this tax on every request that touches those querysets. django-0002 fires on every `dumpdata`/`loaddata` management command and every REST serialization call that specifies a field subset. Django REST Framework uses this path for `fields =` serializer declarations. django-0003 runs at application startup for every model class. Large applications with hundreds of models and many fields pay a quadratic startup cost on every server restart. Django powers tens of thousands of production sites including Instagram (historical), Disqus, Pinterest, and many government and enterprise deployments. ## The Fix **django-0001:** Convert `field_names` to a set before the comprehension: ```python # Before next(values_iter) if f.attname in field_names else DEFERRED # After # CWE-407 fix: frozenset for O(1) contains() instead of O(F) list scan. field_names_set = frozenset(field_names) next(values_iter) if f.attname in field_names_set else DEFERRED ``` **django-0002:** Store `selected_fields` as a frozenset at assignment time: ```python # Before self.selected_fields = fields # After # CWE-407 fix: frozenset for O(1) membership tests in serialize() hot loop. self.selected_fields = frozenset(fields) if fields is not None else None ``` **django-0003/0004:** Replace list with set: ```python # Before used_column_names = [] # After # CWE-407 fix: set for O(1) membership testing. used_column_names = set() ``` ## Patch Fix available: `defects/django/patch/django-0001-0002-field-names-frozenset.patch` Four-location change across `base.py` and `serializers/base.py` and `query.py`. Unit test: 6/6 pass. django-0001 at N=1,000, F=50: **21× speedup**. django-0002: **10× speedup**. django-0003 at F=500: **125× speedup**. django-0004 at F=C=500: **101× speedup**. ## What We Ask A patch is ready for review. 1. Confirm receipt and assign a Django ticket (code.djangoproject.com). 2. Assess severity — django-0001 fires on every deferred queryset iteration; django-0003 fires at every server startup. 3. Coordinate a disclosure date — we are targeting 90 days from first contact. 4. We will credit the Django team in the public disclosure. Preferred acknowledgment format welcome. Contact: see cover email. This brief is confidential until coordinated disclosure.