From 9c869398a89bd084765dd9c347227e43899752f0 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" <68491+gpshead@users.noreply.github.com> Date: Sun, 25 Oct 2026 00:01:05 -0700 Subject: [PATCH] gh-160300: Reject cookies with a negative version in http.cookiejar (GH-160301) DefaultCookiePolicy only applied its domain checks to cookies with version 0 or a positive version. ANT-2026-TA36I7V7 (cherry picked from commit d59d96853be8def13cb419d50991af77c0e26330) Co-authored-by: Gregory P. Smith <68491+gpshead@users.noreply.github.com> --- Lib/http/cookiejar.py | 11 +++++++ Lib/test/test_http_cookiejar.py | 32 +++++++++++++++++-- ...-10-22-08-04-33.gh-issue-160300.q7Zk2B.rst | 3 ++ 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Security/2026-10-22-08-04-33.gh-issue-160300.q7Zk2B.rst diff --git a/Lib/http/cookiejar.py b/Lib/http/cookiejar.py index e622fc36cbfcf62..d15de68dbcaab20 100644 --- a/Lib/http/cookiejar.py +++ b/Lib/http/cookiejar.py @@ -972,6 +972,12 @@ def set_ok_version(self, cookie, request): _debug(" Set-Cookie2 without version attribute (%s=%s)", cookie.name, cookie.value) return False + if cookie.version < 0: + # Neither the Netscape (version 0) nor the RFC 2965 (version > 0) + # rules would apply, so the domain checks would all be skipped. + _debug(" negative version attribute (%s=%s)", + cookie.name, cookie.value) + return False if cookie.version > 0 and not self.rfc2965: _debug(" RFC 2965 cookies are switched off") return False @@ -1112,6 +1118,9 @@ def return_ok(self, cookie, request): return True def return_ok_version(self, cookie, request): + if cookie.version < 0: + _debug(" negative version attribute") + return False if cookie.version > 0 and not self.rfc2965: _debug(" RFC 2965 cookies are switched off") return False @@ -1500,6 +1509,8 @@ def _cookie_from_cookie_tuple(self, tup, request): version = int(version) except ValueError: return None # invalid version, ignore cookie + if version < 0: + return None # invalid version, ignore cookie secure = standard.get("secure", False) # (discard is also set if expires is Absent) discard = standard.get("discard", False) diff --git a/Lib/test/test_http_cookiejar.py b/Lib/test/test_http_cookiejar.py index b90612867fef59c..085ad20ce1d78bc 100644 --- a/Lib/test/test_http_cookiejar.py +++ b/Lib/test/test_http_cookiejar.py @@ -909,10 +909,37 @@ def test_wrong_domain(self): # XXX far from complete c = CookieJar() - interact_2965(c, "http://www.nasty.com/", - 'foo=bar; domain=friendly.org; Version="1"') + interact_2965(c, "http://www.example.com/", + 'foo=bar; domain=python.org; Version="1"') self.assertEqual(len(c), 0) + def test_negative_version(self): + # A negative version is neither Netscape (0) nor RFC 2965 (> 0), so + # such cookies must be neither set nor returned. + for rfc2965 in False, True: + for interact in interact_netscape, interact_2965: + with self.subTest(rfc2965=rfc2965, interact=interact.__name__): + c = CookieJar(DefaultCookiePolicy(rfc2965=rfc2965)) + interact(c, "http://www.example.com/", + 'foo=bar; domain=python.org; Version=-1') + self.assertEqual(len(c), 0) + interact(c, "http://www.example.com/", 'foo=bar; Version=-1') + self.assertEqual(len(c), 0) + + # Such a header is discarded before the policy is consulted. + c = CookieJar() + req = urllib.request.Request("http://www.example.com/") + res = FakeResponse(["Set-Cookie: foo=bar; Version=-1"], + "http://www.example.com/") + self.assertEqual(c.make_cookies(res, req), []) + + # Not set from a response, for example loaded from an LWP cookie file. + c = CookieJar() + c.set_cookie(Cookie(-1, "foo", "bar", None, False, + ".python.org", True, True, "/", True, + False, None, True, None, None, {})) + self.assertEqual(interact_netscape(c, "http://www.python.org/"), "") + def test_strict_domain(self): # Cookies whose domain is a country-code tld like .co.uk should # not be set if CookiePolicy.strict_domain is true. @@ -1412,6 +1439,7 @@ def cookiejar_from_cookie_headers(headers): ["Set-Cookie: b=foo; max-age=oops"], # bad version ["Set-Cookie: b=foo; version=spam"], + ["Set-Cookie: b=foo; version=-1"], ["Set-Cookie:; Expires=%s" % future], ]: c = cookiejar_from_cookie_headers(headers) diff --git a/Misc/NEWS.d/next/Security/2026-10-22-08-04-33.gh-issue-160300.q7Zk2B.rst b/Misc/NEWS.d/next/Security/2026-10-22-08-04-33.gh-issue-160300.q7Zk2B.rst new file mode 100644 index 000000000000000..3cadc6c2c4fd1fb --- /dev/null +++ b/Misc/NEWS.d/next/Security/2026-10-22-08-04-33.gh-issue-160300.q7Zk2B.rst @@ -0,0 +1,3 @@ +Fix :class:`http.cookiejar.DefaultCookiePolicy` accepting and returning +cookies with a negative ``Version`` attribute without checking their domain +against the request host. Such cookies are now ignored.