From 7f22e45821ba714aa01bcc16b79741f253c04f89 Mon Sep 17 00:00:00 2001 From: agu2347 <94227848+agu2347@users.noreply.github.com> Date: Wed, 12 Aug 2026 15:38:55 +0530 Subject: [PATCH] verify: raise MetadataError instead of IndexError for trusted roots with no tlogs (#1822) `Verifier.__init__` picks a Rekor base URL via `trusted_root._inner.tlogs[0].base_url`. If the given `trusted_root` has no transparency log entries, this raises a bare `IndexError` instead of a `sigstore.errors` exception. `Verifier` otherwise documents its failures as `VerificationError` (or subclasses), and callers that only catch that get an unhandled crash. This mirrors the existing, established pattern in `TrustedRoot` itself (`rekor_keyring` / `ct_keyring` / `get_fulcio_certs` all raise `MetadataError` when required trusted-root data is missing) -- per @woodruffw's comment on the issue, this is intentional (a Sigstore instance is expected to have at least one tlog) but should surface as "a more structured error" rather than an unhandled IndexError. Added two unit tests to test_verifier.py: * `Verifier(trusted_root=...)` raises `MetadataError` for a `trusted_root` with an empty `tlogs` list * `Verifier(trusted_root=...)` still succeeds and picks the first tlog's `base_url` when `tlogs` is non-empty, proving the new guard doesn't affect the happy path Fixes #1822 --- sigstore/verify/verifier.py | 4 +++- test/unit/verify/test_verifier.py | 33 ++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/sigstore/verify/verifier.py b/sigstore/verify/verifier.py index 6d4f56d42..efe8e2ecf 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -59,7 +59,7 @@ from sigstore._internal.timestamp import TimestampSource, TimestampVerificationResult from sigstore._internal.trust import KeyringPurpose from sigstore._utils import base64_encode_pem_cert, sha256_digest -from sigstore.errors import CertValidationError, VerificationError +from sigstore.errors import CertValidationError, MetadataError, VerificationError from sigstore.hashes import Hashed from sigstore.models import Bundle, ClientTrustConfig, TrustedRoot from sigstore.verify.policy import VerificationPolicy @@ -92,6 +92,8 @@ def __init__(self, *, trusted_root: TrustedRoot): # this is an ugly hack needed for verifying "detached" materials # In reality we should be choosing the rekor instance based on the logid + if not trusted_root._inner.tlogs: + raise MetadataError("No transparency logs found in trusted root") url = trusted_root._inner.tlogs[0].base_url self._rekor = RekorClient(url) diff --git a/test/unit/verify/test_verifier.py b/test/unit/verify/test_verifier.py index 81287e7f6..673564533 100644 --- a/test/unit/verify/test_verifier.py +++ b/test/unit/verify/test_verifier.py @@ -24,12 +24,43 @@ from sigstore._internal.trust import CertificateAuthority from sigstore.dsse import StatementBuilder, Subject -from sigstore.errors import CertValidationError, VerificationError +from sigstore.errors import CertValidationError, MetadataError, VerificationError from sigstore.models import Bundle from sigstore.verify import policy from sigstore.verify.verifier import Verifier +def test_verifier_init_no_transparency_logs(): + """ + `Verifier.__init__` raises a structured `MetadataError` (rather than an + unhandled `IndexError`) when given a `trusted_root` with no + transparency logs. See #1822. + """ + trusted_root = pretend.stub( + get_fulcio_certs=pretend.call_recorder(lambda: []), + _inner=pretend.stub(tlogs=[]), + ) + + with pytest.raises(MetadataError, match="No transparency logs"): + Verifier(trusted_root=trusted_root) + + +def test_verifier_init_with_transparency_logs(): + """ + `Verifier.__init__` succeeds and picks the first transparency log's + `base_url` when `trusted_root` has at least one, proving the new + `MetadataError` guard doesn't affect the happy path. + """ + tlog = pretend.stub(base_url="https://rekor.example.com") + trusted_root = pretend.stub( + get_fulcio_certs=pretend.call_recorder(lambda: []), + _inner=pretend.stub(tlogs=[tlog]), + ) + + verifier = Verifier(trusted_root=trusted_root) + assert verifier._rekor.url == "https://rekor.example.com/api/v1" + + @pytest.mark.production def test_verifier_production(): verifier = Verifier.production()