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.