# 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.
