Sarah Boyce posted the Django security weblog on October 6. The versions are 6.1.2, 6.0.9, and 5.2.18. Four issues: one low, three moderate. The PGP key is hers, 3955B19851EA96EF. If you are still on 4.2, this post is not your patch train. If you are on 5.2 or 6.x, it is Tuesday.
The 6.1.2 release notes repeat the CVEs and then dump the bugfixes that will actually bite a GIS or admin site: a 4.0 data-loss leftover, a 5.2 infinite loop, and a list of 6.1.1 regressions. We already treated Starlette’s BadHost on the FastAPI side. This is the Django process user, the formset, and the Accept header.
Language codes as cache keys, 500 characters, low
CVE-2026-77050 sits in django.utils.translation.get_supported_language_variant(). Many distinct, very long language codes were used as in-memory cache keys before length was limited. Memory goes up. Boyce’s mitigation: codes longer than 500 characters are rejected or truncated before the lookup. Severity low. Reporter Gleb Lizunov.
Low does not mean skip the release. It means this is the one you will not see on a homepage. Locale middleware and anything that takes Accept-Language seriously should still take the patch, because “many distinct” is a cheap loop. Truncation is not a feature you design around. Do not send 501-character language tags on purpose to see what happens. Upgrade.
This is not i18n news. It is an unbounded key. The same class of bug shows up every time a helper caches attacker-shaped strings. 500 is the new cap, not a recommended locale name.
Headers, quadratic time, unauthenticated
CVE-2026-84429 is moderate and closer to the edge of the network. django.utils.http.parse_header_parameters() had quadratic time when a quoted parameter contained many separators. Unauthenticated requests can reach it through Accept or Content-Type, including content negotiation in HttpRequest.accepts(). A per-call length limit did not bound the combined size of repeated headers.
The undocumented parser now uses Python’s email.message.Message. Parsing of some malformed or unusual values will change. RFC 2231 values with a missing encoding are now decoded. Reporter Jisung Chae.
If you wrapped parse_header_parameters() yourself, read that twice. Behavior change plus a moderate DoS. Tests that asserted exact strings on garbage headers will flake. That is the point of moving onto email.message. Do not pin the old parser.
HttpRequest.accepts() is ordinary. A JSON API that branches on Accept is enough. You do not need a custom middleware to be on the hook. The upgrade is the mitigation. WAF rules on header cardinality are extra, not a substitute.
GIS bytes that make GDAL call the network
CVE-2026-87890 is the one that will surprise GeoDjango people who thought last year’s raster fix was closed. Spatial lookups accepted raster values as raw bytes without requiring django.contrib.gis.gdal.GDALRaster. GDAL opened them through the in-memory virtual filesystem. A VRT document inside those bytes can reference an external raster. GDAL then issues network requests as the Django process user while preparing the lookup.
Boyce says this was overlooked in the fix for CVE-2026-15307. Mitigation is a breaking change: raster bytes must be wrapped in GDALRaster before a spatial lookup. Hexadecimal geometries still pass. Untrusted input should have been validated anyway. Reporter sicksec. Moderate.
If your code does Model.objects.filter(poly__intersects=request_body_bytes), that is the bug. Wrap it or stop taking bytes from the request. The process user is the identity that hits the URL. That is SSRF-adjacent even if the advisory says “request forgery via spatial lookup byte values.” Treat it like a network call you did not intend.
The 6.1.2 notes also fix a class of nested geometry-collection depth-limiter bypasses as CVE 2026-15830, and a GeoDjango 4.0 data-loss case where network rasters were deleted when closed. If you are on 6.1 because of GIS, this is not a drive-by patch. Read the GIS changelog before you call the deploy done.
Formsets, editable primary keys, not BigAutoField
The translation helper is easy to ignore because “language variant” sounds like i18n polish. It is a cache. Keys were attacker-length strings. 500 characters is now the cap. If you log Accept-Language into a dashboard, you will see truncation. That is the patch working.
DoS via accepts() is the one to explain to people who think Django is “behind the WAF.” Repeated headers with quoted junk is a cheap client. Quadratic parsing is a CPU bill. Moving onto email.message.Message is how you leave the homegrown parser. Expect one weird header test to fail. Fix the test, not the patch.
The GIS bytes issue is SSRF-shaped even if the CVE title says lookup. A VRT is an XML recipe. GDAL follows the recipe as the process user. If that user can reach metadata endpoints, you handed the request a network. Wrap GDALRaster. Stop taking raw bytes from query params.
CVE-2026-87975 is the one in the title. Model formsets allowed forged POST data to delete instances outside the limiting queryset, or to create instances through edit-only formsets, when the primary key could be set through the form. Boyce’s examples: a OneToOneField or parent link used as the PK of an inline formset’s model, or a natural or UUID primary key included in the form fields. Default BigAutoField primary keys were not affected. Reporter Seonggwon Yoon. Moderate.
UUID primary keys are fashionable. They are also in the form if you put them in fields. Edit-only formsets are supposed to refuse creates. This bug is how a crafted POST becomes a create or a delete outside the queryset you thought you limited. If your admin inlines use a parent link as PK, you are in the advisory. If every PK is an implicit big auto integer, Boyce just told you that you are outside this CVE. You still need the release for the other three.
Do not “fix” this by hiding the PK in the HTML. The POST is forged. The patch is the fix. After upgrade, regression-test the inline that uses UUID or OneToOne as PK: extra, delete, edit. If you never wrote that test, this week is why.
DEP 19 was governance. This is a formset. Different layer, same project. Upgrade the package. Do not wait for a DSF thread.
Admin inlines are how this CVE becomes a Tuesday incident. A parent OneToOne used as the inline PK is a common profile-extension pattern. A UUID PK on an InlineModelAdmin is a common “we don’t want sequential IDs” pattern. Both are in Boyce’s list. A test that posts an extra form with a guessed UUID is the regression you want. A test that only loads the change page is not.
Natural keys in the form are the third case. If fields includes the slug you treat as a PK, the formset can be talked into a create on an edit-only extra. After 6.1.2, that POST should fail closed. If it does not, you are not on the release you think you are.
Bugfixes that are not the CVE headline, then pip
The 6.1.2 notes are longer than the weblog. Infinite loop in 5.2 tuple lookups (TupleIn) when F() is the left-hand side, ticket 37291. Saving spatial values to srid=None could persist NULL on some databases instead of erroring, 37322. fields.W225 wrongly warned that null has no effect on GeneratedField, 37348. UUID4 persisted uppercase on Oracle; migrate existing rows to lowercase, 37376. Admin still showed objects in inline errors and delete confirmation when delete_confirmation_max_display was 0, 37386. FETCH_PEERS did not batch select_related() instances, extra queries, 37344. RenameModel permission rename bugs, including matching codenames on other models in the same app, 37361. Extra empty breadcrumb on admindocs model index, 37378. Value without an explicit JSONField output field in a top-level JSON lookup on databases without native JSON, 37377.
That list is why “we only care about CVEs” is how you keep a 6.1.1 admin quirk. Pin 6.1.2 or 6.0.9 or 5.2.18, matching the branch you are on. Boyce’s note: report future issues to [email protected], not Trac, not the Forum.
Oracle shops should not skip 37376 because it is not a CVE. Uppercase UUID4 values already in the table will not match lowercase queries after the function is fixed. Plan a data rewrite. Admin shops should not skip 37386: delete_confirmation_max_display = 0 was supposed to hide objects and did not. Permission migrations should not skip 37361 if you chained RenameModel. Those are the three non-CVE items that create support tickets.
Install from the tarball or your usual index. Verify the checksums on the weblog page. If you vendor wheels, rebuild. If you are behind a private mirror, this is the week the mirror needs 6.1.2, not a blog post about supply chain in the abstract.
Supported branches in the weblog are main, 6.1, 6.0, and 5.2. Three tarballs, three checksums. If your pin is Django>=6.1,<6.2, 6.1.2 should flow. If your pin is ==6.1.1, it will not. That is the entire deploy plan for people who are not on GIS.
GIS people have extra homework. Wrap rasters. Re-read CVE-2026-15307 so you know what this follow-up closed. Run the nested geometry tests for CVE 2026-15830. If you still have 4.0-era GeoDjango code closing network rasters, the data-loss note is for you even if you already left 4.0, because the bug lived there and the notes are documenting the class of mistake.
Header parsing will change some fixtures. Search the codebase for parse_header_parameters. If you have zero hits, accepts() is still enough. Put a load test on repeated Accept headers if you are the kind of shop that writes those. If you are not, take the moderate rating as the reason you still patch.
DjangoCon Europe 2027 is already on the weblog sidebar for Innsbruck in February, and DjangoCon US 2027 for Riverside in September. That is conference calendar, not a patch. The patch is 6.1.2 this week. If your team “waits for the sprint,” write the four CVE IDs into the sprint that started Monday. The key ID 3955B19851EA96EF is how you check the tarball, not a souvenir. Keep the checksum file next to the pin.
FastAPI people can keep their Starlette pin story. Django people have a formset and a header parser. Run the four CVE names in your tracker so the next audit does not ask why 6.1.1 is still in prod. Then run the GIS tests if you have them. Then go home.
Discussion
Leave a comment
No comments yet
Be the first to start the conversation.