Opened 61 minutes ago

Last modified 53 minutes ago

#37485 assigned Bug

set_cookie() ignores the cookie flags required by name prefixes and SameSite=None

Reported by: Knk Owned by:
Component: HTTP handling Version: dev
Severity: Normal Keywords: cookies samesite secure httponly
Cc: Knk Triage Stage: Unreviewed
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

HttpResponse.set_cookie() emits the secure and httponly values exactly as it receives them, while
HttpResponse.delete_cookie() already applies the flags a browser requires before it stores a cookie.
The result is a syntactically valid Set-Cookie header that the browser silently discards.

This affects three documented cases:

Cookie name / parameters Required by the browser Emitted by set_cookie()
__Secure-x, __Host-x, __Http-x Secure nothing, unless the caller passes secure=True
__Http-x, __Host-Http-x HttpOnly nothing, unless the caller passes httponly=True
samesite="None" Secure nothing

References: RFC 6265bis section 4.1.3 (cookie name prefixes) and the SameSite=None requirements. The
__Http- and __Host-Http- prefixes come from CHIPS; delete_cookie() already handles them, so the
inconsistency is inside the same class.

The failure is silent. The response is a normal 200 with a Set-Cookie header, the browser drops the
cookie, and the following request arrives without it. A project that sets
SESSION_COOKIE_NAME = "__Host-sessionid" and keeps SESSION_COOKIE_SECURE = False (local development,
or TLS terminated in a reverse proxy) sees a successful login followed by a request with no session, and
nothing appears in the server logs.

Steps to reproduce, current behavior:

>>> from django.http import HttpResponse
>>> r = HttpResponse()
>>> r.set_cookie("__Host-sessionid", "abc")
>>> r.cookies["__Host-sessionid"]["secure"]
''
>>> r.set_cookie("c", "abc", samesite="None")
>>> r.cookies["c"]["secure"]
''

Expected behavior: secure is True in both cases, because a browser will not store either cookie
otherwise. Cookies without a name prefix and without SameSite=None must keep behaving exactly as
before, with the flags the caller passed and nothing else.

Proposed fix: a module-level helper in django/http/response.py that computes the flags required by the
name and by samesite, used by both set_cookie() and delete_cookie() so the two paths cannot
diverge again. The helper only adds flags a browser already requires for that cookie; it never removes
one.

Patch: ​https://github.com/django/django/pull/22261 (branch set-cookie-required-flags), with four
regression tests in tests/responses/test_cookie.py and a paragraph in
docs/ref/request-response.txt.

Verification:

$ python tests/runtests.py responses --parallel 1     # with the patch
Ran 61 tests
OK (skipped=1)

$ python tests/runtests.py responses --parallel 1     # django/http/response.py reverted
FAILED (failures=7, skipped=1)

The seven failures are exactly the new assertions (the prefixed-name loops count one subtest each), so
they measure the change and not the environment.

Not covered by this patch: __Host- also requires Path=/ and no Domain attribute. Enforcing that
means changing the contract of set_cookie() or adding a security.W0xx deployment check that reads
SESSION_COOKIE_NAME / CSRF_COOKIE_NAME; it can be a follow-up rather than part of this diff.

I searched the existing tickets for cookie prefixes, Secure, HttpOnly and SameSite. The open ones I
found, including 34613 (Partitioned support) and the older cookie parsing tickets, do not cover
set_cookie() applying the flags required by name prefixes or by SameSite=None.

Change History (1)

comment:1 by Knk, 53 minutes ago

Status: new → assigned
Note: See TracTickets for help on using tickets.
Back to Top