Promptice

Django: delete_cookie() Fails to Preserve SameSite Attribute

django__django-13195 · cardboard

Django: delete_cookie() Fails to Preserve SameSite Attribute

You are working in the django/django repository at commit 156a2138db20abc89933121e4ff2ee2ce56a173a (Django 3.2 development branch).

Problem

HttpResponse.delete_cookie() does not accept or forward a samesite argument to the underlying set_cookie() call. This causes a silent incompatibility in modern browsers:

When a browser stores a cookie with a SameSite attribute (e.g. SameSite=None; Secure or SameSite=Lax), the deletion Set-Cookie header must repeat that same SameSite attribute, or the browser will treat the deletion header as referring to a different cookie and silently ignore it.

As a result, calling response.delete_cookie("sessionid") when the session cookie was originally set with SESSION_COOKIE_SAMESITE = "None" does not delete the cookie in Chrome 80+ or Firefox 79+ — the session persists even though the application believes it was deleted.

The same silent failure affects:

  • The session middleware when it calls response.delete_cookie() at session end.
  • The messages cookie storage backend when it clears its cookie.

Minimal reproduction

from django.http import HttpResponse

response = HttpResponse()
# Simulate the browser storing: Set-Cookie: token=abc; SameSite=None; Secure
# Later, the app tries to delete the cookie:
response.delete_cookie("token")
print(response.cookies["token"]["samesite"])  # '' — SameSite missing from deletion header

The Set-Cookie header for the deletion is:

Set-Cookie: token=""; expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=/

A compliant deletion for a SameSite=None cookie must be:

Set-Cookie: token=""; expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=/; SameSite=None; Secure

Requirements / Interface

1. HttpResponse.delete_cookie() — new samesite parameter

Extend the method signature in django/http/response.py:

def delete_cookie(self, key, path='/', domain=None, samesite='Lax'):

Behaviour rules:

  • When samesite is provided (non-empty string), delete_cookie() must pass it through to set_cookie() as the samesite keyword argument.
  • When samesite is 'None' (case-insensitive), delete_cookie() must also pass secure=True to set_cookie(), because browsers reject SameSite=None cookies that are not Secure.
  • The samesite parameter must be passed with the same case-insensitivity behaviour that set_cookie() already uses (it normalises to title-case internally, so passing 'none' or 'NONE' is fine — your code just needs to detect the value for the secure flag before forwarding it).

2. Session middleware — django/contrib/sessions/middleware.py

SessionMiddleware.process_response() already reads settings.SESSION_COOKIE_SAMESITE. When it calls response.delete_cookie(), it must now pass samesite=settings.SESSION_COOKIE_SAMESITE so the deletion header matches the original cookie.

3. Messages cookie storage — django/contrib/messages/storage/cookie.py

CookieStorage._delete_cookies() (or equivalent) calls response.delete_cookie(). It must pass samesite=self.samesite (the configured value for the messages cookie, read from settings.MESSAGE_COOKIE_SAMESITE) so the deletion header matches the original cookie.

Files you are expected to touch

| File | What to change |
|---|---|

| django/http/response.py | Add samesite param to delete_cookie(); wire it to set_cookie() |

| django/contrib/sessions/middleware.py | Pass samesite=settings.SESSION_COOKIE_SAMESITE to delete_cookie() |

| django/contrib/messages/storage/cookie.py | Pass samesite=self.samesite to delete_cookie() |

Do not change any test files — the grader applies its own test patch.

Examples

Example A — basic samesite forwarding

from django.http import HttpResponse

r = HttpResponse()
r.delete_cookie("mycookie", samesite="Lax")
print(r.cookies["mycookie"]["samesite"])   # "Lax"
print(r.cookies["mycookie"]["secure"])     # '' (not forced secure for Lax)

Example B — SameSite=None forces Secure

from django.http import HttpResponse

r = HttpResponse()
r.delete_cookie("mycookie", samesite="None")
print(r.cookies["mycookie"]["samesite"])   # "None"
print(r.cookies["mycookie"]["secure"])     # True  (forced because SameSite=None)

Example C — default behaviour unchanged

from django.http import HttpResponse

r = HttpResponse()
r.delete_cookie("mycookie")
# No explicit samesite in the output header — existing callers not broken

Example D — session middleware path

Given SESSION_COOKIE_SAMESITE = "None" in settings:

# After session.flush(), the middleware calls:
response.delete_cookie(
    settings.SESSION_COOKIE_NAME,
    path=settings.SESSION_COOKIE_PATH,
    domain=settings.SESSION_COOKIE_DOMAIN,
    samesite=settings.SESSION_COOKIE_SAMESITE,
)
# Resulting Set-Cookie must contain: SameSite=None; Secure

Constraints

  • Do not modify any test files (tests/ directories or */tests.py). The hidden grader provides its own test patch.
  • Keep the fix minimal — do not refactor unrelated parts of response.py, middleware.py, or cookie.py.
  • Do not change the set_cookie() method's existing signature or semantics.
  • All 24 regression tests listed in the grader must continue to pass.
  • Django's existing set_cookie() already validates samesite and raises ValueError for invalid values; you do not need to add your own validation in delete_cookie().

Scoring

Your submission is accepted when all 5 FAIL_TO_PASS tests pass and all 24 PASS_TO_PASS regression tests continue to pass. Leaderboard ranking additionally considers total tokens used, estimated cost, and wall-clock time.

Container

not started

Visible tests

29

Hidden tests

0

Last run

Not run

29 total0 passed0 failed
1

Test 1

fail to pass

delete cookie samesite (responses.cookie.DeleteCookieTests)

2

Test 2

fail to pass

delete cookie secure samesite none (responses.cookie.DeleteCookieTests)

3

Test 3

fail to pass

session delete on end (sessions tests.tests.SessionMiddlewareTests)

4

Test 4

fail to pass

session delete on end with custom domain and path (sessions tests.tests.SessionMiddlewareTests)

5

Test 5

fail to pass

cookie setings (messages tests.cookie.CookieTests)

6

Test 6

pass to pass

default (responses.cookie.DeleteCookieTests)

7

Test 7

pass to pass

delete cookie secure prefix (responses.cookie.DeleteCookieTests)

8

Test 8

pass to pass

httponly cookie (responses.cookie.SetCookieTests)

9

Test 9

pass to pass

invalid samesite (responses.cookie.SetCookieTests)

10

Test 10

pass to pass

samesite (responses.cookie.SetCookieTests)

11

Test 11

pass to pass

clear (sessions tests.tests.CookieSessionTests)

12

Test 12

pass to pass

custom expiry datetime (sessions tests.tests.CookieSessionTests)

13

Test 13

pass to pass

custom expiry reset (sessions tests.tests.CookieSessionTests)

14

Test 14

pass to pass

cycle (sessions tests.tests.CookieSessionTests)

15

Test 15

pass to pass

decode (sessions tests.tests.CookieSessionTests)

16

Test 16

pass to pass

decode legacy (sessions tests.tests.CookieSessionTests)

17

Test 17

pass to pass

empty session saved (sessions tests.tests.SessionMiddlewareTests)

18

Test 18

pass to pass

flush empty without session cookie doesnt set cookie (sessions tests.tests.SessionMiddlewareTests)

19

Test 19

pass to pass

httponly session cookie (sessions tests.tests.SessionMiddlewareTests)

20

Test 20

pass to pass

no httponly session cookie (sessions tests.tests.SessionMiddlewareTests)

21

Test 21

pass to pass

samesite session cookie (sessions tests.tests.SessionMiddlewareTests)

22

Test 22

pass to pass

secure session cookie (sessions tests.tests.SessionMiddlewareTests)

23

Test 23

pass to pass

session save on 500 (sessions tests.tests.SessionMiddlewareTests)

24

Test 24

pass to pass

session update error redirect (sessions tests.tests.SessionMiddlewareTests)

25

Test 25

pass to pass

add (messages tests.cookie.CookieTests)

26

Test 26

pass to pass

add lazy translation (messages tests.cookie.CookieTests)

27

Test 27

pass to pass

full request response cycle (messages tests.cookie.CookieTests)

28

Test 28

pass to pass

get (messages tests.cookie.CookieTests)

29

Test 29

pass to pass

max cookie length (messages tests.cookie.CookieTests)

README.md

django/django

Loading repository...code-server
Loading...
Workspace Terminal