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.
| Package | Affected versions | Patched versions |
|---|---|---|
scitokensPyPI | < 1.9.6 | 1.9.6 |
Affected products
1Patches
17a237c0f642eAdd scope path matching logic and corresponding unit tests for Enforcer
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- github.com/scitokens/scitokens/commit/7a237c0f642efb9e8c36ac564b745895cca83583nvdPatchWEB
- github.com/scitokens/scitokens/security/advisories/GHSA-w8fp-g9rh-34jhnvdExploitVendor AdvisoryWEB
- github.com/advisories/GHSA-w8fp-g9rh-34jhghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-32716ghsaADVISORY
- github.com/scitokens/scitokens/releases/tag/v1.9.6nvdRelease NotesWEB
News mentions
0No linked articles in our index yet.