https://github.com/python/cpython/commit/2473e7a3c6213e77137a5c51bd5d029ce83b6f45
commit: 2473e7a3c6213e77137a5c51bd5d029ce83b6f45
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: gpshead <[email protected]>
date: 2026-10-25T00:43:35-07:00
summary:

[3.14] gh-160300: Reject cookies with a negative version in http.cookiejar 
(GH-160301) (#160646)

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 <[email protected]>

files:
A Misc/NEWS.d/next/Security/2026-10-22-08-04-33.gh-issue-160300.q7Zk2B.rst
M Lib/http/cookiejar.py
M Lib/test/test_http_cookiejar.py

diff --git a/Lib/http/cookiejar.py b/Lib/http/cookiejar.py
index 5a7aa49bdc75bfb..8060edf336b3d20 100644
--- a/Lib/http/cookiejar.py
+++ b/Lib/http/cookiejar.py
@@ -974,6 +974,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
@@ -1114,6 +1120,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
@@ -1502,6 +1511,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 7c31cff56000b83..f4b53bb1932f53b 100644
--- a/Lib/test/test_http_cookiejar.py
+++ b/Lib/test/test_http_cookiejar.py
@@ -943,10 +943,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.
@@ -1446,6 +1473,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.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to