From 41f06ee5185662b2e360734d415d593bbea412c4 Mon Sep 17 00:00:00 2001 From: AdityasagarR123 Date: Tue, 27 Oct 2026 20:39:13 +0530 Subject: [PATCH 1/2] test(verifier): cover check_sigstore_bundle paths outside model_dir --- tests/test_sigstore_bundle_paths.py | 109 ++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 tests/test_sigstore_bundle_paths.py diff --git a/tests/test_sigstore_bundle_paths.py b/tests/test_sigstore_bundle_paths.py new file mode 100644 index 00000000..ddd64ceb --- /dev/null +++ b/tests/test_sigstore_bundle_paths.py @@ -0,0 +1,109 @@ +import os +import sys +import tempfile +import unittest +from pathlib import Path +from unittest.mock import patch + +SRC = Path(__file__).resolve().parents[1] / "src" +sys.path.insert(0, str(SRC)) + +from verifier import PASS, check_sigstore_bundle # noqa: E402 + + +class SigstoreBundlePathTests(unittest.TestCase): + def setUp(self): + self._tmp = tempfile.TemporaryDirectory() + root = Path(self._tmp.name) + self.model_dir = root / "model" + self.model_dir.mkdir() + (self.model_dir / "model.sig").write_bytes(b"sig-bytes") + self.outside = root / "secret.sig" + self.outside.write_bytes(b"outside-sig-bytes") + + def tearDown(self): + self._tmp.cleanup() + + def test_normal_in_directory_signature_verifies(self): + manifest = { + "signature": "model.sig", + "sigstore_identity": "signer@example.com", + "sigstore_identity_provider": "https://accounts.example.com", + } + with patch("verifier.importlib.util.find_spec", return_value=True): + with patch("verifier.subprocess.run") as mock_run: + mock_run.return_value.returncode = 0 + mock_run.return_value.stdout = "ok" + mock_run.return_value.stderr = "" + result = check_sigstore_bundle( + self.model_dir, + manifest, + expected_identity="signer@example.com", + expected_issuer="https://accounts.example.com", + ) + + self.assertEqual(result.status, PASS) + command = mock_run.call_args.args[0] + sig_index = command.index("--signature") + self.assertEqual(command[sig_index + 1], str((self.model_dir / "model.sig").resolve())) + + def test_parent_relative_name_is_rejected(self): + manifest = { + "signature": "../secret.sig", + "sigstore_identity": "signer@example.com", + "sigstore_identity_provider": "https://accounts.example.com", + } + with self.assertRaises(ValueError): + check_sigstore_bundle(self.model_dir, manifest) + + def test_absolute_path_is_rejected(self): + manifest = { + "signature": str(self.outside), + "sigstore_identity": "signer@example.com", + "sigstore_identity_provider": "https://accounts.example.com", + } + with self.assertRaises(ValueError): + check_sigstore_bundle(self.model_dir, manifest) + + def test_sigstore_bundle_key_is_checked_too(self): + manifest = { + "sigstore_bundle": "../secret.sig", + "sigstore_identity": "signer@example.com", + "sigstore_identity_provider": "https://accounts.example.com", + } + with self.assertRaises(ValueError): + check_sigstore_bundle(self.model_dir, manifest) + + @unittest.skipUnless(hasattr(os, "symlink"), "symlinks unsupported") + def test_symlink_escaping_model_dir_is_rejected(self): + link = self.model_dir / "link.sig" + try: + link.symlink_to(self.outside) + except OSError: + self.skipTest("symlinks require elevated privileges or Developer Mode on Windows") + manifest = { + "signature": "link.sig", + "sigstore_identity": "signer@example.com", + "sigstore_identity_provider": "https://accounts.example.com", + } + with self.assertRaises(ValueError): + check_sigstore_bundle(self.model_dir, manifest) + + @unittest.skipUnless(hasattr(os, "symlink"), "symlinks unsupported") + def test_discovered_symlink_escaping_model_dir_is_rejected(self): + (self.model_dir / "model.sig").unlink() + link = self.model_dir / "model.sig" + try: + link.symlink_to(self.outside) + except OSError: + self.skipTest("symlinks require elevated privileges or Developer Mode on Windows") + manifest = { + "sigstore_identity": "signer@example.com", + "sigstore_identity_provider": "https://accounts.example.com", + } + with self.assertRaises(ValueError): + check_sigstore_bundle(self.model_dir, manifest) + + +if __name__ == "__main__": + unittest.main() From 3b86e8d557e2f7908a15f849bf493403ee83ff8b Mon Sep 17 00:00:00 2001 From: AdityasagarR123 Date: Tue, 27 Oct 2026 20:39:18 +0530 Subject: [PATCH 2/2] fix(verifier): restrict sigstore bundle path to model_dir --- src/verifier.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/verifier.py b/src/verifier.py index b5e88ed2..57cf05e8 100644 --- a/src/verifier.py +++ b/src/verifier.py @@ -207,19 +207,31 @@ def check_sigstore_bundle( expected_issuer: Optional[str] = None, ) -> CheckResult: # Resolve symlinks in the base directory up front - model_dir = Path(model_dir).resolve() + root = Path(model_dir).resolve() signature = manifest.get("signature") or manifest.get("sigstore_bundle") - signature_path = model_dir / signature if signature else _first_existing( - model_dir, ["model.sig", "model.bundle", "sigstore.bundle"] - ) - + if signature: + signature_path = (root / signature).resolve() + if not signature_path.is_relative_to(root): + raise ValueError( + f"Manifest signature artifact {signature!r} resolves outside model directory {root}" + ) + else: + signature_path = _first_existing( + root, ["model.sig", "model.bundle", "sigstore.bundle"] + ) + if signature_path is not None: + signature_path = signature_path.resolve() + if not signature_path.is_relative_to(root): + raise ValueError( + f"Signature artifact {signature_path!r} resolves outside model directory {root}" + ) + if signature_path is None or not signature_path.exists(): status = SKIP if allow_unsigned else FAIL return CheckResult("sigstore_bundle", status, "missing Sigstore/model-signing bundle") - # Resolve symlinks for the specific signature file path - signature_path = signature_path.resolve() + model_dir = root identity = expected_identity or manifest.get("sigstore_identity") provider = expected_issuer or manifest.get("sigstore_identity_provider")