VYPR
High severity8.1NVD Advisory· Published Mar 31, 2026· Updated Apr 3, 2026

CVE-2026-32716

CVE-2026-32716

Description

SciTokens is a reference library for generating and using SciTokens. Prior to version 1.9.6, the Enforcer incorrectly validates scope paths by using a simple prefix match (startswith). This allows a token with access to a specific path (e.g., /john) to also access sibling paths that start with the same prefix (e.g., /johnathan, /johnny), which is an Authorization Bypass. This issue has been patched in version 1.9.6.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
scitokensPyPI
< 1.9.61.9.6

Affected products

1

Patches

1
7a237c0f642e

Add scope path matching logic and corresponding unit tests for Enforcer

https://github.com/scitokens/scitokensDerek WeitzelMar 13, 2026via ghsa
2 files changed · +52 3
  • src/scitokens/scitokens.py+12 3 modified
    @@ -683,6 +683,16 @@ def _check_scope(self, scope):
                 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]
    @@ -693,7 +703,7 @@ def _validate_scp(self, value):
                     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:
    @@ -713,7 +723,7 @@ def _validate_scope(self, value):
                 # 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:
    @@ -722,4 +732,3 @@ def _validate_scope(self, value):
                     authz, norm_path = self._check_scope(scope)
                     self._token_scopes.add((authz, norm_path))
                 return True
    -
    
  • tests/test_scitokens.py+40 0 modified
    @@ -201,6 +201,26 @@ def test_enforce(self):
             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_issuer(self):
             """
             Test the issuer claim, with support for multiple valid issuers.
    @@ -263,6 +283,26 @@ def test_enforce_scope(self):
             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):
             """
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.