File CVE-2026-32716.patch of Package python-scitokens

From 7a237c0f642efb9e8c36ac564b745895cca83583 Mon Sep 17 00:00:00 2001
From: Derek Weitzel <djw8605@gmail.com>
Date: Fri, 13 Mar 2026 10:49:49 -0500
Subject: [PATCH] Add scope path matching logic and corresponding unit tests
 for Enforcer

---
 src/scitokens/scitokens.py | 15 +++++++++++---
 tests/test_scitokens.py    | 40 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 52 insertions(+), 3 deletions(-)

Index: scitokens-1.8.1/src/scitokens/scitokens.py
===================================================================
--- scitokens-1.8.1.orig/src/scitokens/scitokens.py
+++ scitokens-1.8.1/src/scitokens/scitokens.py
@@ -679,6 +679,16 @@ class Enforcer(object):
             norm_path = '/'
         return (authz, norm_path)
 
+    @staticmethod
+    def _scope_path_matches(requested_path, allowed_path):
+        if allowed_path == '/':
+            return True
+        if requested_path == allowed_path:
+            return True
+        if allowed_path.endswith('/'):
+            return requested_path.startswith(allowed_path)
+        return requested_path.startswith(allowed_path + '/')
+
     def _validate_scp(self, value):
         if not isinstance(value, list):
             value = [value]
@@ -689,7 +699,7 @@ class Enforcer(object):
                 norm_requested_path = urltools.normalize_path(self._test_path)
             for scope in value:
                 authz, norm_path = self._check_scope(scope)
-                if (self._test_authz == authz) and norm_requested_path.startswith(norm_path):
+                if (self._test_authz == authz) and self._scope_path_matches(norm_requested_path, norm_path):
                     return True
             return False
         else:
@@ -709,7 +719,7 @@ class Enforcer(object):
             # Split on spaces
             for scope in value.split(" "):
                 authz, norm_path = self._check_scope(scope)
-                if (self._test_authz == authz) and norm_requested_path.startswith(norm_path):
+                if (self._test_authz == authz) and self._scope_path_matches(norm_requested_path, norm_path):
                     return True
             return False
         else:
@@ -718,4 +728,3 @@ class Enforcer(object):
                 authz, norm_path = self._check_scope(scope)
                 self._token_scopes.add((authz, norm_path))
             return True
-
Index: scitokens-1.8.1/tests/test_scitokens.py
===================================================================
--- scitokens-1.8.1.orig/tests/test_scitokens.py
+++ scitokens-1.8.1/tests/test_scitokens.py
@@ -193,6 +193,26 @@ class TestEnforcer(unittest.TestCase):
         with self.assertRaises(scitokens.scitokens.InvalidPathError):
             print(enf.test(self._token, "write", "~/foo"))
 
+    def test_enforce_scp_path_boundaries(self):
+        enf = scitokens.Enforcer(self._test_issuer)
+        enf.add_validator("foo", self.always_accept)
+
+        self._token["scp"] = ["read:/john"]
+        self.assertTrue(enf.test(self._token, "read", "/john"), msg=enf.last_failure)
+        self.assertTrue(enf.test(self._token, "read", "/john/file"), msg=enf.last_failure)
+        self.assertFalse(enf.test(self._token, "read", "/johnathan"), msg=enf.last_failure)
+        self.assertFalse(enf.test(self._token, "read", "/johnny"), msg=enf.last_failure)
+
+        self._token["scp"] = ["read:/john/file"]
+        self.assertFalse(enf.test(self._token, "read", "/john"), msg=enf.last_failure)
+
+        self._token["scp"] = ["read:/"]
+        self.assertTrue(enf.test(self._token, "read", "/arbitrary/path"), msg=enf.last_failure)
+
+        self._token["scp"] = ["read://john"]
+        self.assertTrue(enf.test(self._token, "read", "//john//file"), msg=enf.last_failure)
+        self.assertFalse(enf.test(self._token, "read", "//johnathan"), msg=enf.last_failure)
+
     def test_enforce_scope(self):
         """
         Test the Enforcer object.
@@ -225,6 +245,26 @@ class TestEnforcer(unittest.TestCase):
         with self.assertRaises(scitokens.scitokens.InvalidPathError):
             print(enf.test(self._token, "write", "~/foo"))
 
+    def test_enforce_scope_path_boundaries(self):
+        enf = scitokens.Enforcer(self._test_issuer)
+        enf.add_validator("foo", self.always_accept)
+
+        self._token["scope"] = "read:/john"
+        self.assertTrue(enf.test(self._token, "read", "/john"), msg=enf.last_failure)
+        self.assertTrue(enf.test(self._token, "read", "/john/file"), msg=enf.last_failure)
+        self.assertFalse(enf.test(self._token, "read", "/johnathan"), msg=enf.last_failure)
+        self.assertFalse(enf.test(self._token, "read", "/johnny"), msg=enf.last_failure)
+
+        self._token["scope"] = "read:/john/file"
+        self.assertFalse(enf.test(self._token, "read", "/john"), msg=enf.last_failure)
+
+        self._token["scope"] = "read:/"
+        self.assertTrue(enf.test(self._token, "read", "/arbitrary/path"), msg=enf.last_failure)
+
+        self._token["scope"] = "read://john"
+        self.assertTrue(enf.test(self._token, "read", "//john//file"), msg=enf.last_failure)
+        self.assertFalse(enf.test(self._token, "read", "//johnathan"), msg=enf.last_failure)
+
 
     def test_aud(self):
         """
openSUSE Build Service is sponsored by