Пример
django 6.1: Exercise Django 6.1 requests and migrate signed cookies without reviving ambiguous name-and-salt collisions
Проверенный пример — pypi django 6.1: Exercise Django 6.1 requests and migrate signed cookies without reviving ambiguous name-and-salt collisions. Контракт…
sha256:b2ddcde01ebb61c661640e408c08c66d8bb5bac908bb123b63ee44f86559d282
Эта сеть предлагает одно: образец, который собирается. Она запустила его в песочнице и сохранила подписанную квитанцию. Она ничего не оценивает и ничего не гарантирует — собирается ли тот же код у вас, она не измеряла.
Сколько различных ключей подписи подали пройденную квитанцию контракта. Один — только автор; больше одного — значит, кто-то ещё тоже собрал. Ключ создаётся сам и не имеет зарегистрированной личности, поэтому считаются ключи, а не люди.
MIT-0
Свидетельства выполнения
Заявленное окружение и подписанные запуски разделены, чтобы вы точно видели, что этот образец запускал и где.
- Основа свидетельства
- Подписанный контракт пройден
- Квитанции проверки
- 2
- Ключи подписи, собравшие его
- 2
Заявленная среда
python 3.12 linux x64 python 3.12 python pip
Среды запусков проверки
| Окружение | Контракт | Этапы | Запуск |
|---|---|---|---|
| python 3.12 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · python@1 |
2026-08-17 |
| python 3.12 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · python@1 |
2026-08-18 |
Кейс
HOW- Цель
- Exercise Django 6.1 requests and migrate signed cookies without reviving ambiguous name-and-salt collisions
- Пакеты
- Символы
-
- django.test.Client
- django.test.RequestFactory
- django.http.HttpRequest.get_preferred_type
- django.http.HttpRequest.get_signed_cookie
- django.http.HttpResponse.set_signed_cookie
- django.core.signing.get_cookie_signer
- django.test.override_settings
- Окружение
- python 3.12
- Создан
- 2026-08-17T02:14:30Z
Контракт
- RequestFactory and Client execute without a server or database, keep POST form data separate from repeated query_params, let per-call query_params replace constructor defaults, and negotiate the Accept header.
- HttpResponse.set_signed_cookie and HttpRequest.get_signed_cookie round-trip a value only when the cookie name and caller salt match.
- Django 6.1 rejects a new signed cookie copied from the ('userid', 'v1') pair to the legacy-colliding ('user', 'idv1') pair.
- A cookie made with the old ambiguous 'useridv1' signer salt is rejected by Django 6.1's default settings.
- SIGNED_COOKIE_LEGACY_SALT_FALLBACK=True temporarily accepts the old cookie and emits RemovedInDjango70Warning.
Файлы
- NOTES.md
- csx.json
- requirements.in
- requirements.lock
- requirements.txt
- test/contract.py
Исходный код
# Django 6.1 request and signed-cookie boundaries
The required pre-write sample search returned `NO_SAFE_MATCH` for Django 6.1 in
Python 3.12. This sample therefore records a fresh contract for the current
stable major version.
`Client` and `RequestFactory` run entirely in process. The contract shows that
per-request `query_params` replace constructor defaults, remain separate from a
POST form body, preserve repeated values, and participate in Accept negotiation
without a web server or database.
Django 6.1 also changed signed-cookie salt derivation. Cookie name and caller
salt boundaries are unambiguous by default, so values cannot be moved between
two legacy-colliding name/salt pairs. Old cookies can be accepted temporarily
with `SIGNED_COOKIE_LEGACY_SALT_FALLBACK=True`; enabling that compatibility
setting emits the Django 7.0 removal warning.
`requirements.lock` is a universal, hash-checked lock produced for Python 3.12.
`requirements.txt` repeats the exact installable closure for the verifier.
{"case":{"believed":"A signed cookie remains valid when copied to any cookie name whose name-plus-salt text matches the original pair, and enabling legacy compatibility is a silent permanent fix.","caseId":"case:sha256:f7b6e8b9afc80975d95d1edc312548fc228171b635196fdf27793b55d4b1ffab","contract":["RequestFactory and Client execute without a server or database, keep POST form data separate from repeated query_params, let per-call query_params replace constructor defaults, and negotiate the Accept header.","HttpResponse.set_signed_cookie and HttpRequest.get_signed_cookie round-trip a value only when the cookie name and caller salt match.","Django 6.1 rejects a new signed cookie copied from the ('userid', 'v1') pair to the legacy-colliding ('user', 'idv1') pair.","A cookie made with the old ambiguous 'useridv1' signer salt is rejected by Django 6.1's default settings.","SIGNED_COOKIE_LEGACY_SALT_FALLBACK=True temporarily accepts the old cookie and emits RemovedInDjango70Warning."],"goal":"Exercise Django 6.1 requests and migrate signed cookies without reviving ambiguous name-and-salt collisions","kind":"HOW","packages":["pkg:pypi/django@6.1"],"schemaVersion":1,"symbols":["django.test.Client","django.test.RequestFactory","django.http.HttpRequest.get_preferred_type","django.http.HttpRequest.get_signed_cookie","django.http.HttpResponse.set_signed_cookie","django.core.signing.get_cookie_signer","django.test.override_settings"]},"contractCommand":["python","test/contract.py"],"environment":{"arch":"x64","ecosystem":"pypi","executionContext":"python","language":"python","os":"linux","packageManager":"pip","runtime":"python","runtimeVersion":"3.12","schemaVersion":1},"license":"MIT-0","packages":["pkg:pypi/django@6.1"],"schemaVersion":1,"symbols":["django.test.Client","django.test.RequestFactory","django.http.HttpRequest.get_preferred_type","django.http.HttpRequest.get_signed_cookie","django.http.HttpResponse.set_signed_cookie","django.core.signing.get_cookie_signer","django.test.override_settings"],"verifierAdapter":"python@1"}
Django==6.1
# This file was autogenerated by uv via the following command:
# uv pip compile requirements.in --universal --generate-hashes --output-file requirements.lock --python-version 3.12
asgiref==3.12.1 \
--hash=sha256:59dcb51c272ad209d59bed5708a64a333083e86017d7fcdd67498eeab7784340 \
--hash=sha256:fe386d1c2bff7259ea95929266d12a8cf9a8b5a1c2598402967d8792e7a7c094
# via django
django==6.1 \
--hash=sha256:6c132cd980c9392b06807d4ca52d72530d631dc65a85d9dacede00a780cefbbe \
--hash=sha256:86a2aacd59b817e4d6ac2ebfe22356c58f66f7b24e503f71b7c2fead677ee48b
# via -r requirements.in
sqlparse==0.6.0 \
--hash=sha256:113c35c75365ab9cc9c7231d68c6428fb11c085fc8e9eb1ad659b7ddbf6cd2b9 \
--hash=sha256:b861c0288ce2fa56209a9a6412d2e066ac664b3873b89c26c9d8415e8e32996f
# via django
tzdata==2026.3 ; sys_platform == 'win32' \
--hash=sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415 \
--hash=sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931
# via django
# Fully pinned, hash-checked dependency closure for Django 6.1.
asgiref==3.12.1 \
--hash=sha256:59dcb51c272ad209d59bed5708a64a333083e86017d7fcdd67498eeab7784340 \
--hash=sha256:fe386d1c2bff7259ea95929266d12a8cf9a8b5a1c2598402967d8792e7a7c094
django==6.1 \
--hash=sha256:6c132cd980c9392b06807d4ca52d72530d631dc65a85d9dacede00a780cefbbe \
--hash=sha256:86a2aacd59b817e4d6ac2ebfe22356c58f66f7b24e503f71b7c2fead677ee48b
sqlparse==0.6.0 \
--hash=sha256:113c35c75365ab9cc9c7231d68c6428fb11c085fc8e9eb1ad659b7ddbf6cd2b9 \
--hash=sha256:b861c0288ce2fa56209a9a6412d2e066ac664b3873b89c26c9d8415e8e32996f
tzdata==2026.3 ; sys_platform == 'win32' \
--hash=sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415 \
--hash=sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931
import json
import warnings
from django.conf import settings
if not settings.configured:
settings.configure(
ALLOWED_HOSTS=["testserver"],
DEBUG=False,
INSTALLED_APPS=[],
MIDDLEWARE=[],
ROOT_URLCONF=__name__,
SECRET_KEY="public-sample-secret-not-for-production",
SECRET_KEY_FALLBACKS=[],
)
import django
django.setup()
from django.core import signing
from django.core.signing import BadSignature
from django.http import JsonResponse
from django.test import Client, RequestFactory, override_settings
from django.urls import path
from django.utils.deprecation import RemovedInDjango70Warning
def inspect_request(request):
return JsonResponse(
{
"method": request.method,
"tags": request.GET.getlist("tag"),
"name": request.POST.get("name"),
"preferred": request.get_preferred_type(
["text/html", "application/json"]
),
}
)
def issue_cookie(request):
response = JsonResponse({"issued": True})
response.set_signed_cookie(
"userid", "trusted-value", salt="v1", httponly=True, samesite="Lax"
)
return response
def read_original_cookie(request):
return JsonResponse(
{"value": request.get_signed_cookie("userid", salt="v1")}
)
def read_colliding_cookie(request):
try:
value = request.get_signed_cookie("user", salt="idv1")
except BadSignature:
return JsonResponse({"accepted": False})
return JsonResponse({"accepted": True, "value": value})
urlpatterns = [
path("inspect/", inspect_request),
path("issue/", issue_cookie),
path("read-original/", read_original_cookie),
path("read-collision/", read_colliding_cookie),
]
def response_json(response):
return json.loads(response.content.decode("utf-8"))
factory = RequestFactory(
headers={"accept": "application/json"},
query_params={"tag": "constructor-default"},
)
factory_request = factory.post(
"/inspect/",
data={"name": "factory-body"},
query_params={"tag": ["stable", "current"]},
)
factory_payload = response_json(inspect_request(factory_request))
assert factory_payload == {
"method": "POST",
"tags": ["stable", "current"],
"name": "factory-body",
"preferred": "application/json",
}
client = Client(
headers={"accept": "application/json"},
query_params={"tag": "constructor-default"},
)
client_payload = client.post(
"/inspect/",
data={"name": "client-body"},
query_params={"tag": ["one", "two"]},
).json()
assert client_payload == {
"method": "POST",
"tags": ["one", "two"],
"name": "client-body",
"preferred": "application/json",
}
issued = client.get("/issue/")
signed_value = issued.cookies["userid"].value
assert issued.cookies["userid"]["httponly"] is True
assert issued.cookies["userid"]["samesite"] == "Lax"
assert client.get("/read-original/").json() == {"value": "trusted-value"}
# These pairs collided under Django's old cookie_name + salt derivation:
# ("userid", "v1") -> "useridv1"
# ("user", "idv1") -> "useridv1"
# Django 6.1's default signer keeps those boundaries unambiguous.
client.cookies["user"] = signed_value
assert client.get("/read-collision/").json() == {"accepted": False}
# Model a cookie emitted by Django 6.0 and earlier using the legacy salt.
legacy_value = signing.get_cookie_signer(salt="useridv1").sign("legacy-value")
client.cookies["user"] = legacy_value
assert client.get("/read-collision/").json() == {"accepted": False}
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always", RemovedInDjango70Warning)
with override_settings(SIGNED_COOKIE_LEGACY_SALT_FALLBACK=True):
legacy_payload = client.get("/read-collision/").json()
assert legacy_payload == {"accepted": True, "value": "legacy-value"}
assert any(issubclass(item.category, RemovedInDjango70Warning) for item in caught)
print("Django 6.1 request and signed-cookie contract passed")