Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-Flask-Cors for 
openSUSE:Factory checked in at 2026-04-02 17:41:17
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-Flask-Cors (Old)
 and      /work/SRC/openSUSE:Factory/.python-Flask-Cors.new.21863 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-Flask-Cors"

Thu Apr  2 17:41:17 2026 rev:14 rq:1344234 version:6.0.2

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-Flask-Cors/python-Flask-Cors.changes      
2025-05-05 22:59:11.227510298 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-Flask-Cors.new.21863/python-Flask-Cors.changes
   2026-04-02 17:42:29.494640971 +0200
@@ -1,0 +2,16 @@
+Wed Apr  1 22:28:19 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 6.0.2 (bsc#1239846, CVE-2024-6839,
+      bsc#1239847, CVE-2024-6844,
+      bsc#1239848, CVE-2024-6866):
+  * Invert regex sorting to make it correctly match the intent
+    (sorting by specificity descending) #391
+  * Path specificity ordering has changed to improve specificity.
+    This may break users who expected the previous incorrect
+    ordering.
+  * [CVE-2024-6839] Sort Paths by Regex Specificity
+  * [CVE-2024-6844] Replace use of (urllib) unquote_plus with
+    unquote
+  * [CVE-2024-6866] Case Sensitive Request Path Matching
+
+-------------------------------------------------------------------

Old:
----
  flask_cors-5.0.1.tar.gz

New:
----
  flask_cors-6.0.2.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-Flask-Cors.spec ++++++
--- /var/tmp/diff_new_pack.qgpINP/_old  2026-04-02 17:42:30.994702924 +0200
+++ /var/tmp/diff_new_pack.qgpINP/_new  2026-04-02 17:42:31.022704080 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package python-Flask-Cors
 #
-# Copyright (c) 2025 SUSE LLC
+# Copyright (c) 2026 SUSE LLC and contributors
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -18,13 +18,14 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-Flask-Cors
-Version:        5.0.1
+Version:        6.0.2
 Release:        0
 Summary:        A Flask extension adding a decorator for CORS support
 License:        MIT
 URL:            https://github.com/corydolphin/flask-cors
 Source:         
https://github.com/corydolphin/flask-cors/archive/refs/tags/%{version}.tar.gz#/flask_cors-%{version}.tar.gz
 BuildRequires:  %{python_module Flask >= 0.9}
+BuildRequires:  %{python_module Werkzeug >= 0.7}
 BuildRequires:  %{python_module base >= 3.9}
 BuildRequires:  %{python_module pip}
 BuildRequires:  %{python_module pytest}

++++++ flask_cors-5.0.1.tar.gz -> flask_cors-6.0.2.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flask-cors-5.0.1/flask_cors/core.py 
new/flask-cors-6.0.2/flask_cors/core.py
--- old/flask-cors-5.0.1/flask_cors/core.py     2025-02-24 04:51:54.000000000 
+0100
+++ new/flask-cors-6.0.2/flask_cors/core.py     2025-12-12 18:55:57.000000000 
+0100
@@ -69,14 +69,17 @@
         # resource of '*', which is not actually a valid regexp.
         resources = [(re_fix(k), v) for k, v in resources.items()]
 
-        # Sort by regex length to provide consistency of matching and
-        # to provide a proxy for specificity of match. E.G. longer
-        # regular expressions are tried first.
-        def pattern_length(pair):
-            maybe_regex, _ = pair
-            return len(get_regexp_pattern(maybe_regex))
+        # Sort patterns with static (literal) paths first, then by regex 
specificity
+        def sort_key(pair):
+            pattern, _ = pair
+            if isinstance(pattern, RegexObject):
+                return (1, 0, -pattern.pattern.count("/"), 
-len(pattern.pattern))
+            elif probably_regex(pattern):
+                return (1, 1, -pattern.count("/"), -len(pattern))
+            else:
+                return (0, 0, -pattern.count("/"), -len(pattern))
 
-        return sorted(resources, key=pattern_length, reverse=True)
+        return sorted(resources, key=sort_key)
 
     elif isinstance(resources, str):
         return [(re_fix(resources), {})]
@@ -121,9 +124,10 @@
         if wildcard and options.get("send_wildcard"):
             LOG.debug("Allowed origins are set to '*'. Sending wildcard CORS 
header.")
             return ["*"]
-        # If the value of the Origin header is a case-sensitive match
-        # for any of the values in list of origins
-        elif try_match_any(request_origin, origins):
+        # If the value of the Origin header is a case-insensitive match
+        # for any of the values in list of origins.
+        # NOTE: Per RFC 1035 and RFC 4343 schemes and hostnames are case 
insensitive.
+        elif try_match_any_pattern(request_origin, origins, 
caseSensitive=False):
             LOG.debug(
                 "The request's Origin header matches. Sending CORS headers.",
             )
@@ -164,7 +168,7 @@
         request_headers = [h.strip() for h in acl_request_headers.split(",")]
 
         # any header that matches in the allow_headers
-        matching_headers = filter(lambda h: try_match_any(h, 
options.get("allow_headers")), request_headers)
+        matching_headers = filter(lambda h: try_match_any_pattern(h, 
options.get("allow_headers"), caseSensitive=False), request_headers)
 
         return ", ".join(sorted(matching_headers))
 
@@ -277,22 +281,31 @@
     return r".*" if reg == r"*" else reg
 
 
-def try_match_any(inst, patterns):
-    return any(try_match(inst, pattern) for pattern in patterns)
-
+def try_match_any_pattern(inst, patterns, caseSensitive=True):
+    return any(try_match_pattern(inst, pattern, caseSensitive) for pattern in 
patterns)
 
-def try_match(request_origin, maybe_regex):
-    """Safely attempts to match a pattern or string to a request origin."""
-    if isinstance(maybe_regex, RegexObject):
-        return re.match(maybe_regex, request_origin)
-    elif probably_regex(maybe_regex):
-        return re.match(maybe_regex, request_origin, flags=re.IGNORECASE)
-    else:
+def try_match_pattern(value, pattern, caseSensitive=True):
+    """
+    Safely attempts to match a pattern or string to a value. This
+    function can be used to match request origins, headers, or paths.
+    The value of caseSensitive should be set in accordance to the
+    data being compared e.g. origins and headers are case insensitive
+    whereas paths are case-sensitive
+    """
+    if isinstance(pattern, RegexObject):
+        return re.match(pattern, value)
+    if probably_regex(pattern):
+        flags = 0 if caseSensitive else re.IGNORECASE
         try:
-            return request_origin.lower() == maybe_regex.lower()
-        except AttributeError:
-            return request_origin == maybe_regex
-
+            return re.match(pattern, value, flags=flags)
+        except re.error:
+            return False
+    try:
+        v = str(value)
+        p = str(pattern)
+        return v == p if caseSensitive else v.casefold() == p.casefold()
+    except Exception:
+        return value == pattern
 
 def get_cors_options(appInstance, *dicts):
     """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flask-cors-5.0.1/flask_cors/extension.py 
new/flask-cors-6.0.2/flask_cors/extension.py
--- old/flask-cors-5.0.1/flask_cors/extension.py        2025-02-24 
04:51:54.000000000 +0100
+++ new/flask-cors-6.0.2/flask_cors/extension.py        2025-12-12 
18:55:57.000000000 +0100
@@ -1,9 +1,9 @@
 import logging
-from urllib.parse import unquote_plus
+from urllib.parse import unquote
 
 from flask import request
 
-from .core import ACL_ORIGIN, get_cors_options, get_regexp_pattern, 
parse_resources, set_cors_headers, try_match
+from .core import ACL_ORIGIN, get_cors_options, get_regexp_pattern, 
parse_resources, set_cors_headers, try_match_pattern
 
 LOG = logging.getLogger(__name__)
 
@@ -188,9 +188,9 @@
         if resp.headers is not None and resp.headers.get(ACL_ORIGIN):
             LOG.debug("CORS have been already evaluated, skipping")
             return resp
-        normalized_path = unquote_plus(request.path)
+        normalized_path = unquote(request.path)
         for res_regex, res_options in resources:
-            if try_match(normalized_path, res_regex):
+            if try_match_pattern(normalized_path, res_regex, 
caseSensitive=True):
                 LOG.debug(
                     "Request to '%r' matches CORS resource '%s'. Using 
options: %s",
                     request.path,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flask-cors-5.0.1/pyproject.toml 
new/flask-cors-6.0.2/pyproject.toml
--- old/flask-cors-5.0.1/pyproject.toml 2025-02-24 04:51:54.000000000 +0100
+++ new/flask-cors-6.0.2/pyproject.toml 2025-12-12 18:55:57.000000000 +0100
@@ -3,8 +3,9 @@
 version = "0.0.1"
 description = "A Flask extension simplifying CORS support"
 authors = [{ name = "Cory Dolphin", email = "[email protected]" }]
-readme = "README.md"
+readme = "README.rst"
 keywords = ['python']
+license = "MIT"
 requires-python = ">=3.9,<4.0"
 classifiers = [
     "Intended Audience :: Developers",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flask-cors-5.0.1/tests/core/helper_tests.py 
new/flask-cors-6.0.2/tests/core/helper_tests.py
--- old/flask-cors-5.0.1/tests/core/helper_tests.py     2025-02-24 
04:51:54.000000000 +0100
+++ new/flask-cors-6.0.2/tests/core/helper_tests.py     1970-01-01 
01:00:00.000000000 +0100
@@ -1,90 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-    Tests for helper and utility methods
-    TODO: move integration tests (e.g. all that test a full request cycle)
-    into smaller, broken-up unit tests to simplify testing.
-    ~~~~
-    Flask-CORS is a simple extension to Flask allowing you to support cross
-    origin resource sharing (CORS) using a simple decorator.
-
-    :copyright: (c) 2016 by Cory Dolphin.
-    :license: MIT, see LICENSE for more details.
-"""
-
-import unittest
-
-from flask_cors.core import *
-
-
-class InternalsTestCase(unittest.TestCase):
-    def test_try_match(self):
-        self.assertFalse(try_match('www.com/foo', 'www.com/fo'))
-        self.assertTrue(try_match('www.com/foo', 'www.com/fo*'))
-
-    def test_flexible_str_str(self):
-        self.assertEqual(flexible_str('Bar, Foo, Qux'), 'Bar, Foo, Qux')
-
-    def test_flexible_str_set(self):
-        self.assertEqual(flexible_str({'Foo', 'Bar', 'Qux'}),
-                          'Bar, Foo, Qux')
-
-    def test_serialize_options(self):
-        try:
-            serialize_options({
-                'origins': r'*',
-                'allow_headers': True,
-                'supports_credentials': True,
-                'send_wildcard': True
-            })
-            self.assertFalse(True, "A Value Error should have been raised.")
-        except ValueError:
-            pass
-
-    def test_get_allow_headers_empty(self):
-        options = serialize_options({'allow_headers': r'*'})
-
-        self.assertEqual(get_allow_headers(options, ''), None)
-        self.assertEqual(get_allow_headers(options, None), None)
-
-    def test_get_allow_headers_matching(self):
-        options = serialize_options({'allow_headers': r'*'})
-
-        self.assertEqual(get_allow_headers(options, 'X-FOO'), 'X-FOO')
-        self.assertEqual(
-            get_allow_headers(options, 'X-Foo, X-Bar'),
-            'X-Bar, X-Foo'
-        )
-
-    def test_get_allow_headers_matching_none(self):
-        options = serialize_options({'allow_headers': r'X-FLASK-.*'})
-
-        self.assertEqual(get_allow_headers(options, 'X-FLASK-CORS'),
-                          'X-FLASK-CORS')
-        self.assertEqual(
-            get_allow_headers(options, 'X-NOT-FLASK-CORS'),
-            ''
-        )
-
-    def test_parse_resources_sorted(self):
-        resources = parse_resources({
-            '/foo': {'origins': 'http://foo.com'},
-            re.compile(r'/.*'): {
-                'origins': 'http://some-domain.com'
-            },
-            re.compile(r'/api/v1/.*'): {
-                'origins': 'http://specific-domain.com'
-            }
-        })
-
-        self.assertEqual(
-            [r[0] for r in resources],
-            [re.compile(r'/api/v1/.*'), '/foo', re.compile(r'/.*')]
-        )
-
-    def test_probably_regex(self):
-        self.assertTrue(probably_regex("http://*.example.com";))
-        self.assertTrue(probably_regex("*"))
-        self.assertFalse(probably_regex("http://example.com";))
-        self.assertTrue(probably_regex(r"http://[\w].example.com";))
-        self.assertTrue(probably_regex(r"http://\w+.example.com";))
-        self.assertTrue(probably_regex("https?://example.com"))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flask-cors-5.0.1/tests/core/test_helpers.py 
new/flask-cors-6.0.2/tests/core/test_helpers.py
--- old/flask-cors-5.0.1/tests/core/test_helpers.py     1970-01-01 
01:00:00.000000000 +0100
+++ new/flask-cors-6.0.2/tests/core/test_helpers.py     2025-12-12 
18:55:57.000000000 +0100
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+"""
+    Tests for helper and utility methods
+    TODO: move integration tests (e.g. all that test a full request cycle)
+    into smaller, broken-up unit tests to simplify testing.
+    ~~~~
+    Flask-CORS is a simple extension to Flask allowing you to support cross
+    origin resource sharing (CORS) using a simple decorator.
+
+    :copyright: (c) 2016 by Cory Dolphin.
+    :license: MIT, see LICENSE for more details.
+"""
+
+import unittest
+
+from flask_cors.core import *
+
+
+class InternalsTestCase(unittest.TestCase):
+    def test_try_match_pattern(self):
+        self.assertFalse(try_match_pattern('www.com/foo', 'www.com/fo', 
caseSensitive=True))
+        self.assertTrue(try_match_pattern('www.com/foo', 'www.com/fo*', 
caseSensitive=True))
+        self.assertTrue(try_match_pattern('www.com', 'WwW.CoM', 
caseSensitive=False))
+        self.assertTrue(try_match_pattern('/foo', '/fo*', caseSensitive=True))
+        self.assertFalse(try_match_pattern('/foo', '/Fo*', caseSensitive=True))
+
+    def test_flexible_str_str(self):
+        self.assertEqual(flexible_str('Bar, Foo, Qux'), 'Bar, Foo, Qux')
+
+    def test_flexible_str_set(self):
+        self.assertEqual(flexible_str({'Foo', 'Bar', 'Qux'}),
+                          'Bar, Foo, Qux')
+
+    def test_serialize_options(self):
+        try:
+            serialize_options({
+                'origins': r'*',
+                'allow_headers': True,
+                'supports_credentials': True,
+                'send_wildcard': True
+            })
+            self.assertFalse(True, "A Value Error should have been raised.")
+        except ValueError:
+            pass
+
+    def test_get_allow_headers_empty(self):
+        options = serialize_options({'allow_headers': r'*'})
+
+        self.assertEqual(get_allow_headers(options, ''), None)
+        self.assertEqual(get_allow_headers(options, None), None)
+
+    def test_get_allow_headers_matching(self):
+        options = serialize_options({'allow_headers': r'*'})
+
+        self.assertEqual(get_allow_headers(options, 'X-FOO'), 'X-FOO')
+        self.assertEqual(
+            get_allow_headers(options, 'X-Foo, X-Bar'),
+            'X-Bar, X-Foo'
+        )
+
+    def test_get_allow_headers_matching_none(self):
+        options = serialize_options({'allow_headers': r'X-FLASK-.*'})
+
+        self.assertEqual(get_allow_headers(options, 'X-FLASK-CORS'),
+                          'X-FLASK-CORS')
+        self.assertEqual(
+            get_allow_headers(options, 'X-NOT-FLASK-CORS'),
+            ''
+        )
+
+    def test_parse_resources_sorted(self):
+        resources = parse_resources({
+            '/foo': {'origins': 'http://foo.com'},
+            re.compile(r'/.*'): {
+                'origins': 'http://some-domain.com'
+            },
+            re.compile(r'/api/v1/.*'): {
+                'origins': 'http://specific-domain.com'
+            }
+        })
+
+        self.assertEqual(
+            [r[0] for r in resources],
+            ['/foo', re.compile(r'/api/v1/.*'), re.compile(r'/.*')]
+        )
+
+    def test_probably_regex(self):
+        self.assertTrue(probably_regex("http://*.example.com";))
+        self.assertTrue(probably_regex("*"))
+        self.assertFalse(probably_regex("http://example.com";))
+        self.assertTrue(probably_regex(r"http://[\w].example.com";))
+        self.assertTrue(probably_regex(r"http://\w+.example.com";))
+        self.assertTrue(probably_regex("https?://example.com"))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/flask-cors-5.0.1/tests/extension/test_app_extension.py 
new/flask-cors-6.0.2/tests/extension/test_app_extension.py
--- old/flask-cors-5.0.1/tests/extension/test_app_extension.py  2025-02-24 
04:51:54.000000000 +0100
+++ new/flask-cors-6.0.2/tests/extension/test_app_extension.py  2025-12-12 
18:55:57.000000000 +0100
@@ -378,5 +378,61 @@
             self.assertEqual(resp.status_code, 200)
 
 
+class AppExtensionPlusInPath(FlaskCorsTestCase):
+    '''
+        Regression test for CVE-2024-6844:
+        Ensures that we correctly differentiate '+' from ' ' in URL paths.
+    '''
+
+    def setUp(self):
+        self.app = Flask(__name__)
+        CORS(self.app, resources={
+            r'/service\+path': {'origins': ['http://foo.com']},
+            r'/service path': {'origins': ['http://bar.com']},
+        })
+
+        @self.app.route('/service+path')
+        def plus_path():
+            return 'plus'
+
+        @self.app.route('/service path')
+        def space_path():
+            return 'space'
+
+        self.client = self.app.test_client()
+
+    def test_plus_path_origin_allowed(self):
+        '''
+        Ensure that CORS matches + literally and allows the correct origin
+        '''
+        response = self.client.get('/service+path', headers={'Origin': 
'http://foo.com'})
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.headers.get(ACL_ORIGIN), 'http://foo.com')
+
+    def test_space_path_origin_allowed(self):
+        '''
+        Ensure that CORS treats /service path differently and allows correct 
origin
+        '''
+        response = self.client.get('/service%20path', headers={'Origin': 
'http://bar.com'})
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.headers.get(ACL_ORIGIN), 'http://bar.com')
+
+    def test_plus_path_rejects_other_origin(self):
+        '''
+        Origin not allowed for + path should be rejected
+        '''
+        response = self.client.get('/service+path', headers={'Origin': 
'http://bar.com'})
+        self.assertEqual(response.status_code, 200)
+        self.assertIsNone(response.headers.get(ACL_ORIGIN))
+
+    def test_space_path_rejects_other_origin(self):
+        '''
+        Origin not allowed for space path should be rejected
+        '''
+        response = self.client.get('/service%20path', headers={'Origin': 
'http://foo.com'})
+        self.assertEqual(response.status_code, 200)
+        self.assertIsNone(response.headers.get(ACL_ORIGIN))
+
+
 if __name__ == "__main__":
     unittest.main()

Reply via email to