Repository: sigstore/cosign
Pull request #3889 (2024-09-25 19:31:43)

Add support for new bundle specification for attesting/verifying OCI image attestations
#### Summary

This PR adds support for the new [Cosign Bundle Specification](https://github.com/sigstore/cosign/blob/main/specs/BUNDLE_SPEC.md) in `cosign attest` and `cosign verify-attestation`.

Related: https://github.com/sigstore/cosign/issues/3139

To test, run the following (replacing `MY_IDENTITY`, `MY_ISSUER`, `MY_TRUSTED_ROOT` and `MY_IMAGE` as needed -- trusted root is optional). Note that the new OCI support requires passing `--new-bundle-format` into both commands.

```shell
go run ./cmd/cosign attest --predicate my-predicate.json --new-bundle-format MY_IMAGE
go run ./cmd/cosign verify-attestation --certificate-identity MY_IDENTITY --certificate-oidc-issuer MY_ISSUER --new-bundle-format --trusted-root=MY_TRUSTED_ROOT MY_IMAGE
```

Full example (using `crane`, but can instead use `docker tag`/`docker push`):

```shell
make cosign
docker run -d -p 5050:5000 ghcr.io/project-zot/zot-linux-arm64:latest
crane copy busybox:latest localhost:5050/busybox
echo '{"foo": "bar"}' > predicate.json
./cosign attest --predicate predicate.json --new-bundle-format localhost:5050/busybox:latest
# Dex workflow, assuming GitHub as provider
./cosign verify-attestation --certificate-identity=yourname@example.com --certificate-oidc-issuer=https://github.com/login/oauth --new-bundle-format localhost:5050/busybox:latest
```

To show that it uses the OCI 1.1 referrers API, you can use `oras`:

```shell
oras discover localhost:5050/busybox:latest
Discovered 1 artifact referencing latest
Digest: sha256:db142d433cdde11f10ae479dbf92f3b13d693fd1c91053da9979728cceb1dc68

Artifact Type                                   Digest
application/vnd.dev.sigstore.bundle.v0.3+json   sha256:3fb46d845fe437667a6b3ed45d2b11dea11c43f8fbe76dd642eb71de2a9b8b77
```

<!--
Thanks for opening a pull request! Please do not just delete this text.  The three fields below are mandatory.

Please remember to:
- This PR requires an issue. If it is a new feature, the issue should proceed the PR and will have allowed sufficient time for discussions to take place. Please use
issue tags such as "Closes #XYZ" or "Resolves sigstore/repo-name#XYZ".
  [documentation](https://docs.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)
- ensure your commits are signed-off, as sigstore uses the [DCO](https://en.wikipedia.org/wiki/Developer_Certificate_of_Origin) using `git commit -s`, or `git commit -s --amend` if you want to amend already existing commits
- lastly, ensure there are no merge commits!

Thank you :)
-->

<!--
 Explain the **motivation** for making this change. What existing problem does the pull request solve? How can reviewers test this PR?
-->

#### Release Note
<!--
Add a release note for each of the following conditions:

* Config changes (additions, deletions, updates)
* API additions—new endpoint, new response fields, or newly accepted request parameters
* Database changes (any)
* Websocket additions or changes
* Anything noteworthy to an administrator running private sigstore instances (err on the side of over-communicating)
* New features and improvements, including behavioural changes, UI changes and CLI changes
* Bug fixes and fixes of previous known issues
* Deprecation warnings, breaking changes, or compatibility notes

If no release notes are required write NONE. Use past-tense.

-->

#### Documentation
<!--

Does this change require an update to documentation? How will users implement your new feature?

Please reference a PR within https://docs.sigstore.dev

-->


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/sigstore/cosign/pull/3889?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore) Report
Attention: Patch coverage is `1.68067%` with `351 lines` in your changes missing coverage. Please review.
> Project coverage is 35.55%. Comparing base [(`2ef6022`)](https://app.codecov.io/gh/sigstore/cosign/commit/2ef6022f3f52299a56e0faacaed9eb2203b6b4a6?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore) to head [(`c540689`)](https://app.codecov.io/gh/sigstore/cosign/commit/c5406897cad9aacdbd7bdd8aee5582a580db2f6a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore).
> Report is 236 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/sigstore/cosign/pull/3889?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore) | Patch % | Lines |
|---|---|---|
| [pkg/cosign/verify.go](https://app.codecov.io/gh/sigstore/cosign/pull/3889?src=pr&el=tree&filepath=pkg%2Fcosign%2Fverify.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore#diff-cGtnL2Nvc2lnbi92ZXJpZnkuZ28=) | 0.00% | [131 Missing :warning: ](https://app.codecov.io/gh/sigstore/cosign/pull/3889?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore) |
| [pkg/oci/remote/write.go](https://app.codecov.io/gh/sigstore/cosign/pull/3889?src=pr&el=tree&filepath=pkg%2Foci%2Fremote%2Fwrite.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore#diff-cGtnL29jaS9yZW1vdGUvd3JpdGUuZ28=) | 0.00% | [89 Missing :warning: ](https://app.codecov.io/gh/sigstore/cosign/pull/3889?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore) |
| [cmd/cosign/cli/verify/verify\_attestation.go](https://app.codecov.io/gh/sigstore/cosign/pull/3889?src=pr&el=tree&filepath=cmd%2Fcosign%2Fcli%2Fverify%2Fverify_attestation.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore#diff-Y21kL2Nvc2lnbi9jbGkvdmVyaWZ5L3ZlcmlmeV9hdHRlc3RhdGlvbi5nbw==) | 0.00% | [36 Missing :warning: ](https://app.codecov.io/gh/sigstore/cosign/pull/3889?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore) |
| [cmd/cosign/cli/attest/attest.go](https://app.codecov.io/gh/sigstore/cosign/pull/3889?src=pr&el=tree&filepath=cmd%2Fcosign%2Fcli%2Fattest%2Fattest.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore#diff-Y21kL2Nvc2lnbi9jbGkvYXR0ZXN0L2F0dGVzdC5nbw==) | 0.00% | [32 Missing :warning: ](https://app.codecov.io/gh/sigstore/cosign/pull/3889?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore) |
| [pkg/oci/remote/signatures.go](https://app.codecov.io/gh/sigstore/cosign/pull/3889?src=pr&el=tree&filepath=pkg%2Foci%2Fremote%2Fsignatures.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore#diff-cGtnL29jaS9yZW1vdGUvc2lnbmF0dXJlcy5nbw==) | 0.00% | [20 Missing :warning: ](https://app.codecov.io/gh/sigstore/cosign/pull/3889?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore) |
| [cmd/cosign/cli/attest/common.go](https://app.codecov.io/gh/sigstore/cosign/pull/3889?src=pr&el=tree&filepath=cmd%2Fcosign%2Fcli%2Fattest%2Fcommon.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore#diff-Y21kL2Nvc2lnbi9jbGkvYXR0ZXN0L2NvbW1vbi5nbw==) | 0.00% | [10 Missing :warning: ](https://app.codecov.io/gh/sigstore/cosign/pull/3889?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore) |
| [pkg/cosign/verify\_bundle.go](https://app.codecov.io/gh/sigstore/cosign/pull/3889?src=pr&el=tree&filepath=pkg%2Fcosign%2Fverify_bundle.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore#diff-cGtnL2Nvc2lnbi92ZXJpZnlfYnVuZGxlLmdv) | 0.00% | [10 Missing :warning: ](https://app.codecov.io/gh/sigstore/cosign/pull/3889?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore) |
| [cmd/cosign/cli/verify/verify.go](https://app.codecov.io/gh/sigstore/cosign/pull/3889?src=pr&el=tree&filepath=cmd%2Fcosign%2Fcli%2Fverify%2Fverify.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore#diff-Y21kL2Nvc2lnbi9jbGkvdmVyaWZ5L3ZlcmlmeS5nbw==) | 0.00% | [8 Missing :warning: ](https://app.codecov.io/gh/sigstore/cosign/pull/3889?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore) |
| [cmd/cosign/cli/attest/attest\_blob.go](https://app.codecov.io/gh/sigstore/cosign/pull/3889?src=pr&el=tree&filepath=cmd%2Fcosign%2Fcli%2Fattest%2Fattest_blob.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore#diff-Y21kL2Nvc2lnbi9jbGkvYXR0ZXN0L2F0dGVzdF9ibG9iLmdv) | 14.28% | [6 Missing :warning: ](https://app.codecov.io/gh/sigstore/cosign/pull/3889?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore) |
| [cmd/cosign/cli/verify.go](https://app.codecov.io/gh/sigstore/cosign/pull/3889?src=pr&el=tree&filepath=cmd%2Fcosign%2Fcli%2Fverify.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore#diff-Y21kL2Nvc2lnbi9jbGkvdmVyaWZ5Lmdv) | 0.00% | [3 Missing :warning: ](https://app.codecov.io/gh/sigstore/cosign/pull/3889?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore) |
| ... and [4 more](https://app.codecov.io/gh/sigstore/cosign/pull/3889?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore) | |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #3889      +/-   ##
==========================================
- Coverage   40.10%   35.55%   -4.55%     
==========================================
  Files         155      210      +55     
  Lines       10044    13693    +3649     
==========================================
+ Hits         4028     4869     +841     
- Misses       5530     8198    +2668     
- Partials      486      626     +140     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/sigstore/cosign/pull/3889?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore).

codysoyland: I rebased and squashed all commits into one as there have been a couple of items refactored into separate PRs (https://github.com/sigstore/cosign/pull/4006 and https://github.com/sigstore/cosign/pull/4013) and this ongoing PR was getting a bit messy.

I still need to finish #4013 and rebase this one again, at which point I will be ready for another review pass.

Thanks everyone who has been patiently reviewing and helping me iterate on this!
codysoyland: This has been rebased onto the merged refactors and is ready for review.
codysoyland: > Do I understand the Codecov report correctly that 1% of these changes are covered via tests? Any thoughts on how to increase that?

True, there are no remaining unit tests in this PR after the last rebase, but Codecov may not take into account the new e2e tests which should cover quite a bit. I'll try manually running coverage and see what I can reasonably cover with more tests.
dmitris: @codysoyland - thanks for pushing this forward, eagerly awaiting this feature!  If I can help with testing or anything else, please let me know! 

Could you resolve the conflicts, please? Thanks.
codysoyland: > @codysoyland - thanks for pushing this forward, eagerly awaiting this feature! If I can help with testing or anything else, please let me know!

Thank you! I'd be interested in any manual test results you may have!

> Could you resolve the conflicts, please? Thanks.

I just rebased and resolved the conflict. 👍🏻 
codysoyland: I generated coverage with e2e testing. [Here is a link to the report.](https://gist.github.com/codysoyland/de83ab37e3c5422442e87e1443de8ff5) TL;DR: Most of the "happy paths" are covered, but many error cases are not covered, which is pretty typical for the e2e tests. I'll take another pass to see if there's any low hanging fruit for additional testing, but I don't think the coverage of this PR is much different from our existing coverage, however I would still like to improve it.
dmitris: @codysoyland "stepped on" some of the files you're modifying with https://github.com/sigstore/cosign/pull/3889 - could you rebase and resolve the conflicts, please?

Patch:
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 917599cf..6facd3ee 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -56,7 +56,7 @@ jobs:
         persist-credentials: false
 
     - name: Utilize Go Module Cache
-      uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
+      uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
       with:
         path: |
           ~/go/pkg/mod
diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml
index ba4e84de..5fd14f33 100644
--- a/.github/workflows/conformance.yml
+++ b/.github/workflows/conformance.yml
@@ -39,6 +39,6 @@ jobs:
 
       - run: make cosign conformance
 
-      - uses: sigstore/sigstore-conformance@d658ea74a060aeabae78f8a379167f219dc38c38 # v0.0.16
+      - uses: sigstore/sigstore-conformance@640e7dfb715518eeeb492910c6d244cedcc6cfea # v0.0.17
         with:
           entrypoint: ${{ github.workspace }}/conformance
diff --git a/.github/workflows/validate-release.yml b/.github/workflows/validate-release.yml
index 827798b7..2b909e91 100644
--- a/.github/workflows/validate-release.yml
+++ b/.github/workflows/validate-release.yml
@@ -26,14 +26,14 @@ jobs:
   check-signature:
     runs-on: ubuntu-latest
     container:
-      image: ghcr.io/sigstore/cosign/cosign:v2.4.2-dev@sha256:b69af124cb744c22eba955ebaf9514b42b1556811211e1cd744331350ccd815a
+      image: ghcr.io/sigstore/cosign/cosign:v2.4.3-dev@sha256:ed76c008e733aa64d257f754a02eb07b251525ea8dc08f40974baec317dea8c9
 
     steps:
       - name: Check Signature
         run: |
-          cosign verify ghcr.io/gythialy/golang-cross:v1.23.6-0@sha256:6e8da612570af388b204b833c0925c669d00f7654cf964e5bc3e808013234c05 \
+          cosign verify ghcr.io/gythialy/golang-cross:v1.24.0-0@sha256:5a528c7ccfb6db79769f79a09120141a6f251ba0660046dac2108bd14eedb82d \
           --certificate-oidc-issuer https://token.actions.githubusercontent.com \
-          --certificate-identity "https://github.com/gythialy/golang-cross/.github/workflows/release-golang-cross.yml@refs/tags/v1.23.6-0"
+          --certificate-identity "https://github.com/gythialy/golang-cross/.github/workflows/release-golang-cross.yml@refs/tags/v1.24.0-0"
         env:
           TUF_ROOT: /tmp
 
@@ -43,7 +43,7 @@ jobs:
       - check-signature
 
     container:
-      image: ghcr.io/gythialy/golang-cross:v1.23.6-0@sha256:6e8da612570af388b204b833c0925c669d00f7654cf964e5bc3e808013234c05
+      image: ghcr.io/gythialy/golang-cross:v1.24.0-0@sha256:5a528c7ccfb6db79769f79a09120141a6f251ba0660046dac2108bd14eedb82d
       volumes:
         - /usr:/host_usr
         - /opt:/host_opt
diff --git a/cmd/cosign/cli/options/verify.go b/cmd/cosign/cli/options/verify.go
index 6ab0d7ca..33924c0d 100644
--- a/cmd/cosign/cli/options/verify.go
+++ b/cmd/cosign/cli/options/verify.go
@@ -31,6 +31,8 @@ type CommonVerifyOptions struct {
 	ExperimentalOCI11     bool
 	PrivateInfrastructure bool
 	UseSignedTimestamps   bool
+	NewBundleFormat       bool
+	TrustedRootPath       string
 }
 
 func (o *CommonVerifyOptions) AddFlags(cmd *cobra.Command) {
@@ -42,7 +44,7 @@ func (o *CommonVerifyOptions) AddFlags(cmd *cobra.Command) {
 			"Optionally may contain intermediate CA certificates, and may contain the leaf TSA certificate if not present in the timestamp")
 
 	cmd.Flags().BoolVar(&o.UseSignedTimestamps, "use-signed-timestamps", false,
-		"use signed timestamps if available")
+		"verify rfc3161 timestamps")
 
 	cmd.Flags().BoolVar(&o.IgnoreTlog, "insecure-ignore-tlog", false,
 		"ignore transparency log verification, to be used when an artifact signature has not been uploaded to the transparency log. Artifacts "+
@@ -56,6 +58,13 @@ func (o *CommonVerifyOptions) AddFlags(cmd *cobra.Command) {
 
 	cmd.Flags().IntVar(&o.MaxWorkers, "max-workers", cosign.DefaultMaxWorkers,
 		"the amount of maximum workers for parallel executions")
+
+	cmd.Flags().StringVar(&o.TrustedRootPath, "trusted-root", "",
+		"Path to a Sigstore TrustedRoot JSON file. Requires --new-bundle-format to be set.")
+
+	// TODO: have this default to true as a breaking change
+	cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", false,
+		"expect the signature/attestation to be packaged in a Sigstore bundle")
 }
 
 // VerifyOptions is the top level wrapper for the `verify` command.
@@ -161,11 +170,9 @@ func (o *VerifyAttestationOptions) AddFlags(cmd *cobra.Command) {
 
 // VerifyBlobOptions is the top level wrapper for the `verify blob` command.
 type VerifyBlobOptions struct {
-	Key             string
-	Signature       string
-	BundlePath      string
-	NewBundleFormat bool
-	TrustedRootPath string
+	Key        string
+	Signature  string
+	BundlePath string
 
 	SecurityKey         SecurityKeyOptions
 	CertVerify          CertVerifyOptions
@@ -193,13 +200,6 @@ func (o *VerifyBlobOptions) AddFlags(cmd *cobra.Command) {
 	cmd.Flags().StringVar(&o.BundlePath, "bundle", "",
 		"path to bundle FILE")
 
-	// TODO: have this default to true as a breaking change
-	cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", false,
-		"output bundle in new format that contains all verification material")
-
-	cmd.Flags().StringVar(&o.TrustedRootPath, "trusted-root", "",
-		"path to trusted root FILE")
-
 	cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp", "",
 		"path to RFC3161 timestamp FILE")
 }
@@ -222,11 +222,9 @@ func (o *VerifyDockerfileOptions) AddFlags(cmd *cobra.Command) {
 
 // VerifyBlobAttestationOptions is the top level wrapper for the `verify-blob-attestation` command.
 type VerifyBlobAttestationOptions struct {
-	Key             string
-	SignaturePath   string
-	BundlePath      string
-	NewBundleFormat bool
-	TrustedRootPath string
+	Key           string
+	SignaturePath string
+	BundlePath    string
 
 	PredicateOptions
 	CheckClaims bool
@@ -258,13 +256,6 @@ func (o *VerifyBlobAttestationOptions) AddFlags(cmd *cobra.Command) {
 	cmd.Flags().StringVar(&o.BundlePath, "bundle", "",
 		"path to bundle FILE")
 
-	// TODO: have this default to true as a breaking change
-	cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", false,
-		"output bundle in new format that contains all verification material")
-
-	cmd.Flags().StringVar(&o.TrustedRootPath, "trusted-root", "",
-		"path to trusted root FILE")
-
 	cmd.Flags().BoolVar(&o.CheckClaims, "check-claims", true,
 		"if true, verifies the provided blob's sha256 digest exists as an in-toto subject within the attestation. If false, only the DSSE envelope is verified.")
 
diff --git a/cmd/cosign/cli/sign/sign.go b/cmd/cosign/cli/sign/sign.go
index 1289e7b1..f7bd51fd 100644
--- a/cmd/cosign/cli/sign/sign.go
+++ b/cmd/cosign/cli/sign/sign.go
@@ -599,7 +599,6 @@ func (c *SignerVerifier) Close() {
 
 func (c *SignerVerifier) Bytes(ctx context.Context) ([]byte, error) {
 	if c.Cert != nil {
-		ui.Infof(ctx, "using ephemeral certificate:\n%s", string(c.Cert))
 		return c.Cert, nil
 	}
 
diff --git a/cmd/cosign/cli/verify.go b/cmd/cosign/cli/verify.go
index d306756c..f79af288 100644
--- a/cmd/cosign/cli/verify.go
+++ b/cmd/cosign/cli/verify.go
@@ -26,7 +26,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-const ignoreTLogMessage = "Skipping tlog verification is an insecure practice that lacks of transparency and auditability verification for the %s."
+const ignoreTLogMessage = "Skipping tlog verification is an insecure practice that lacks transparency and auditability verification for the %s."
 
 func Verify() *cobra.Command {
 	o := &options.VerifyOptions{}
@@ -218,6 +218,7 @@ against the transparency log.`,
 
 			v := &verify.VerifyAttestationCommand{
 				RegistryOptions:              o.Registry,
+				CommonVerifyOptions:          o.CommonVerifyOptions,
 				CheckClaims:                  o.CheckClaims,
 				CertVerifyOptions:            o.CertVerify,
 				CertRef:                      o.CertVerify.Cert,
@@ -333,9 +334,9 @@ The blob may be specified as a path to a file or - for stdin.`,
 				Slot:                 o.SecurityKey.Slot,
 				RekorURL:             o.Rekor.URL,
 				BundlePath:           o.BundlePath,
-				NewBundleFormat:      o.NewBundleFormat,
 				RFC3161TimestampPath: o.RFC3161TimestampPath,
 				TSACertChainPath:     o.CommonVerifyOptions.TSACertChainPath,
+				NewBundleFormat:      o.CommonVerifyOptions.NewBundleFormat,
 			}
 			verifyBlobCmd := &verify.VerifyBlobCmd{
 				KeyOpts:                      ko,
@@ -345,7 +346,6 @@ The blob may be specified as a path to a file or - for stdin.`,
 				CARoots:                      o.CertVerify.CARoots,
 				CAIntermediates:              o.CertVerify.CAIntermediates,
 				SigRef:                       o.Signature,
-				TrustedRootPath:              o.TrustedRootPath,
 				CertGithubWorkflowTrigger:    o.CertVerify.CertGithubWorkflowTrigger,
 				CertGithubWorkflowSHA:        o.CertVerify.CertGithubWorkflowSha,
 				CertGithubWorkflowName:       o.CertVerify.CertGithubWorkflowName,
@@ -356,6 +356,7 @@ The blob may be specified as a path to a file or - for stdin.`,
 				Offline:                      o.CommonVerifyOptions.Offline,
 				IgnoreTlog:                   o.CommonVerifyOptions.IgnoreTlog,
 				UseSignedTimestamps:          o.CommonVerifyOptions.UseSignedTimestamps,
+				TrustedRootPath:              o.CommonVerifyOptions.TrustedRootPath,
 			}
 
 			ctx, cancel := context.WithTimeout(cmd.Context(), ro.Timeout)
@@ -404,9 +405,9 @@ The blob may be specified as a path to a file.`,
 				Slot:                 o.SecurityKey.Slot,
 				RekorURL:             o.Rekor.URL,
 				BundlePath:           o.BundlePath,
-				NewBundleFormat:      o.NewBundleFormat,
 				RFC3161TimestampPath: o.RFC3161TimestampPath,
 				TSACertChainPath:     o.CommonVerifyOptions.TSACertChainPath,
+				NewBundleFormat:      o.CommonVerifyOptions.NewBundleFormat,
 			}
 			v := verify.VerifyBlobAttestationCommand{
 				KeyOpts:                      ko,
@@ -414,7 +415,6 @@ The blob may be specified as a path to a file.`,
 				CheckClaims:                  o.CheckClaims,
 				SignaturePath:                o.SignaturePath,
 				CertVerifyOptions:            o.CertVerify,
-				TrustedRootPath:              o.TrustedRootPath,
 				CertRef:                      o.CertVerify.Cert,
 				CertChain:                    o.CertVerify.CertChain,
 				CARoots:                      o.CertVerify.CARoots,
@@ -429,6 +429,7 @@ The blob may be specified as a path to a file.`,
 				Offline:                      o.CommonVerifyOptions.Offline,
 				IgnoreTlog:                   o.CommonVerifyOptions.IgnoreTlog,
 				UseSignedTimestamps:          o.CommonVerifyOptions.UseSignedTimestamps,
+				TrustedRootPath:              o.CommonVerifyOptions.TrustedRootPath,
 			}
 			// We only use the blob if we are checking claims.
 			if len(args) == 0 && o.CheckClaims {
diff --git a/cmd/cosign/cli/verify/verify.go b/cmd/cosign/cli/verify/verify.go
index f5fc86bf..9d79846d 100644
--- a/cmd/cosign/cli/verify/verify.go
+++ b/cmd/cosign/cli/verify/verify.go
@@ -41,6 +41,7 @@ import (
 	"github.com/sigstore/cosign/v2/pkg/cosign/pkcs11key"
 	"github.com/sigstore/cosign/v2/pkg/oci"
 	sigs "github.com/sigstore/cosign/v2/pkg/signature"
+	"github.com/sigstore/sigstore-go/pkg/root"
 	"github.com/sigstore/sigstore/pkg/cryptoutils"
 	"github.com/sigstore/sigstore/pkg/signature"
 	"github.com/sigstore/sigstore/pkg/signature/payload"
@@ -51,6 +52,7 @@ import (
 type VerifyCommand struct {
 	options.RegistryOptions
 	options.CertVerifyOptions
+	options.CommonVerifyOptions
 	CheckClaims                  bool
 	KeyRef                       string
 	CertRef                      string
@@ -134,7 +136,16 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {
 		MaxWorkers:                   c.MaxWorkers,
 		ExperimentalOCI11:            c.ExperimentalOCI11,
 		UseSignedTimestamps:          c.TSACertChainPath != "" || c.UseSignedTimestamps,
+		NewBundleFormat:              c.NewBundleFormat,
 	}
+
+	if c.TrustedRootPath != "" {
+		co.TrustedMaterial, err = root.NewTrustedRootFromPath(c.TrustedRootPath)
+		if err != nil {
+			return fmt.Errorf("loading trusted root: %w", err)
+		}
+	}
+
 	if c.CheckClaims {
 		co.ClaimVerifier = cosign.SimpleClaimVerifier
 	}
diff --git a/doc/cosign_dockerfile_verify.md b/doc/cosign_dockerfile_verify.md
index 0b64d614..3ee9b0d3 100644
--- a/doc/cosign_dockerfile_verify.md
+++ b/doc/cosign_dockerfile_verify.md
@@ -77,6 +77,7 @@ cosign dockerfile verify [flags]
       --key string                                                                               path to the public key file, KMS URI or Kubernetes Secret
       --local-image                                                                              whether the specified image is a path to an image saved locally via 'cosign save'
       --max-workers int                                                                          the amount of maximum workers for parallel executions (default 10)
+      --new-bundle-format                                                                        expect the signature/attestation to be packaged in a Sigstore bundle
       --offline                                                                                  only allow offline verification
   -o, --output string                                                                            output format for the signing image information (json|text) (default "json")
       --payload string                                                                           payload path or remote URL
@@ -95,7 +96,8 @@ cosign dockerfile verify [flags]
       --sk                                                                                       whether to use a hardware security key
       --slot string                                                                              security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management)
       --timestamp-certificate-chain string                                                       path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA certificate. Optionally may contain intermediate CA certificates, and may contain the leaf TSA certificate if not present in the timestamp
-      --use-signed-timestamps                                                                    use signed timestamps if available
+      --trusted-root string                                                                      Path to a Sigstore TrustedRoot JSON file. Requires --new-bundle-format to be set.
+      --use-signed-timestamps                                                                    verify rfc3161 timestamps
 ```
 
 ### Options inherited from parent commands
diff --git a/doc/cosign_manifest_verify.md b/doc/cosign_manifest_verify.md
index 4bd31816..0914e691 100644
--- a/doc/cosign_manifest_verify.md
+++ b/doc/cosign_manifest_verify.md
@@ -71,6 +71,7 @@ cosign manifest verify [flags]
       --key string                                                                               path to the public key file, KMS URI or Kubernetes Secret
       --local-image                                                                              whether the specified image is a path to an image saved locally via 'cosign save'
       --max-workers int                                                                          the amount of maximum workers for parallel executions (default 10)
+      --new-bundle-format                                                                        expect the signature/attestation to be packaged in a Sigstore bundle
       --offline                                                                                  only allow offline verification
   -o, --output string                                                                            output format for the signing image information (json|text) (default "json")
       --payload string                                                                           payload path or remote URL
@@ -89,7 +90,8 @@ cosign manifest verify [flags]
       --sk                                                                                       whether to use a hardware security key
       --slot string                                                                              security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management)
       --timestamp-certificate-chain string                                                       path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA certificate. Optionally may contain intermediate CA certificates, and may contain the leaf TSA certificate if not present in the timestamp
-      --use-signed-timestamps                                                                    use signed timestamps if available
+      --trusted-root string                                                                      Path to a Sigstore TrustedRoot JSON file. Requires --new-bundle-format to be set.
+      --use-signed-timestamps                                                                    verify rfc3161 timestamps
 ```
 
 ### Options inherited from parent commands
diff --git a/doc/cosign_verify-blob.md b/doc/cosign_verify-blob.md
index 92655ccc..cb966881 100644
--- a/doc/cosign_verify-blob.md
+++ b/doc/cosign_verify-blob.md
@@ -84,7 +84,7 @@ cosign verify-blob [flags]
       --insecure-ignore-tlog                            ignore transparency log verification, to be used when an artifact signature has not been uploaded to the transparency log. Artifacts cannot be publicly verified when not included in a log
       --key string                                      path to the public key file, KMS URI or Kubernetes Secret
       --max-workers int                                 the amount of maximum workers for parallel executions (default 10)
-      --new-bundle-format                               output bundle in new format that contains all verification material
+      --new-bundle-format                               expect the signature/attestation to be packaged in a Sigstore bundle
       --offline                                         only allow offline verification
       --private-infrastructure                          skip transparency log verification when verifying artifacts in a privately deployed infrastructure
       --rekor-url string                                address of rekor STL server (default "https://rekor.sigstore.dev")
@@ -94,8 +94,8 @@ cosign verify-blob [flags]
       --sk                                              whether to use a hardware security key
       --slot string                                     security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management)
       --timestamp-certificate-chain string              path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA certificate. Optionally may contain intermediate CA certificates, and may contain the leaf TSA certificate if not present in the timestamp
-      --trusted-root string                             path to trusted root FILE
-      --use-signed-timestamps                           use signed timestamps if available
+      --trusted-root string                             Path to a Sigstore TrustedRoot JSON file. Requires --new-bundle-format to be set.
+      --use-signed-timestamps                           verify rfc3161 timestamps
 ```
 
 ### Options inherited from parent commands
diff --git a/doc/cosign_verify.md b/doc/cosign_verify.md
index de9e10dd..42ee11c7 100644
--- a/doc/cosign_verify.md
+++ b/doc/cosign_verify.md
@@ -98,6 +98,7 @@ cosign verify [flags]
       --key string                                                                               path to the public key file, KMS URI or Kubernetes Secret
       --local-image                                                                              whether the specified image is a path to an image saved locally via 'cosign save'
       --max-workers int                                                                          the amount of maximum workers for parallel executions (default 10)
+      --new-bundle-format                                                                        expect the signature/attestation to be packaged in a Sigstore bundle
       --offline                                                                                  only allow offline verification
   -o, --output string                                                                            output format for the signing image information (json|text) (default "json")
       --payload string                                                                           payload path or remote URL
@@ -116,7 +117,8 @@ cosign verify [flags]
       --sk                                                                                       whether to use a hardware security key
       --slot string                                                                              security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management)
       --timestamp-certificate-chain string                                                       path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA certificate. Optionally may contain intermediate CA certificates, and may contain the leaf TSA certificate if not present in the timestamp
-      --use-signed-timestamps                                                                    use signed timestamps if available
+      --trusted-root string                                                                      Path to a Sigstore TrustedRoot JSON file. Requires --new-bundle-format to be set.
+      --use-signed-timestamps                                                                    verify rfc3161 timestamps
 ```
 
 ### Options inherited from parent commands
diff --git a/go.mod b/go.mod
index 84eb993d..5d765f28 100644
--- a/go.mod
+++ b/go.mod
@@ -6,13 +6,13 @@ require (
 	cuelang.org/go v0.12.0
 	github.com/ThalesIgnite/crypto11 v1.2.5
 	github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.9.1
-	github.com/buildkite/agent/v3 v3.92.1
+	github.com/buildkite/agent/v3 v3.93.1
 	github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589
 	github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46
 	github.com/depcheck-test/depcheck-test v0.0.0-20220607135614-199033aaa936
 	github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7
 	github.com/dustin/go-humanize v1.0.1
-	github.com/go-jose/go-jose/v3 v3.0.3
+	github.com/go-jose/go-jose/v3 v3.0.4
 	github.com/go-openapi/runtime v0.28.0
 	github.com/go-openapi/strfmt v0.23.0
 	github.com/go-openapi/swag v0.23.0
@@ -49,12 +49,12 @@ require (
 	github.com/theupdateframework/go-tuf/v2 v2.0.2
 	github.com/transparency-dev/merkle v0.0.2
 	github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1
-	gitlab.com/gitlab-org/api/client-go v0.123.0
+	gitlab.com/gitlab-org/api/client-go v0.124.0
 	golang.org/x/crypto v0.35.0
 	golang.org/x/oauth2 v0.27.0
 	golang.org/x/sync v0.11.0
 	golang.org/x/term v0.29.0
-	google.golang.org/api v0.222.0
+	google.golang.org/api v0.223.0
 	google.golang.org/protobuf v1.36.5
 	k8s.io/api v0.28.3
 	k8s.io/apimachinery v0.28.3
@@ -65,7 +65,7 @@ require (
 
 require (
 	cloud.google.com/go v0.116.0 // indirect
-	cloud.google.com/go/auth v0.14.1 // indirect
+	cloud.google.com/go/auth v0.15.0 // indirect
 	cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect
 	cloud.google.com/go/compute/metadata v0.6.0 // indirect
 	cloud.google.com/go/iam v1.2.2 // indirect
@@ -76,7 +76,7 @@ require (
 	github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/provider v0.14.0 // indirect
 	github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
 	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect
-	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1 // indirect
+	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 // indirect
 	github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
 	github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.0 // indirect
 	github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0 // indirect
@@ -89,7 +89,7 @@ require (
 	github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
 	github.com/Azure/go-autorest/logger v0.2.1 // indirect
 	github.com/Azure/go-autorest/tracing v0.6.0 // indirect
-	github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // indirect
+	github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect
 	github.com/Microsoft/go-winio v0.6.2 // indirect
 	github.com/OneOfOne/xxhash v1.2.8 // indirect
 	github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c // indirect
@@ -107,21 +107,21 @@ require (
 	github.com/aliyun/credentials-go v1.3.2 // indirect
 	github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
 	github.com/aws/aws-sdk-go v1.55.6 // indirect
-	github.com/aws/aws-sdk-go-v2 v1.36.1 // indirect
-	github.com/aws/aws-sdk-go-v2/config v1.29.6 // indirect
-	github.com/aws/aws-sdk-go-v2/credentials v1.17.59 // indirect
-	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.28 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 // indirect
+	github.com/aws/aws-sdk-go-v2 v1.36.2 // indirect
+	github.com/aws/aws-sdk-go-v2/config v1.29.7 // indirect
+	github.com/aws/aws-sdk-go-v2/credentials v1.17.60 // indirect
+	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.29 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.33 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.33 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
 	github.com/aws/aws-sdk-go-v2/service/ecr v1.40.3 // indirect
 	github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.31.2 // indirect
-	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 // indirect
-	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13 // indirect
-	github.com/aws/aws-sdk-go-v2/service/kms v1.37.18 // indirect
-	github.com/aws/aws-sdk-go-v2/service/sso v1.24.15 // indirect
-	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.14 // indirect
-	github.com/aws/aws-sdk-go-v2/service/sts v1.33.14 // indirect
+	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
+	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.14 // indirect
+	github.com/aws/aws-sdk-go-v2/service/kms v1.37.19 // indirect
+	github.com/aws/aws-sdk-go-v2/service/sso v1.24.16 // indirect
+	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.15 // indirect
+	github.com/aws/aws-sdk-go-v2/service/sts v1.33.15 // indirect
 	github.com/aws/smithy-go v1.22.2 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/blang/semver v3.5.1+incompatible // indirect
@@ -249,7 +249,7 @@ require (
 	github.com/zeebo/errs v1.4.0 // indirect
 	go.mongodb.org/mongo-driver v1.14.0 // indirect
 	go.opentelemetry.io/auto/sdk v1.1.0 // indirect
-	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect
 	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
 	go.opentelemetry.io/otel v1.34.0 // indirect
 	go.opentelemetry.io/otel/metric v1.34.0 // indirect
@@ -267,7 +267,7 @@ require (
 	golang.org/x/tools v0.29.0 // indirect
 	google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect
 	google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20250212204824-5a70512c5d8b // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 // indirect
 	google.golang.org/grpc v1.70.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
diff --git a/go.sum b/go.sum
index 724bae39..74b767a3 100644
--- a/go.sum
+++ b/go.sum
@@ -1,8 +1,8 @@
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
 cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
-cloud.google.com/go/auth v0.14.1 h1:AwoJbzUdxA/whv1qj3TLKwh3XX5sikny2fc40wUl+h0=
-cloud.google.com/go/auth v0.14.1/go.mod h1:4JHUxlGXisL0AW8kXPtUF6ztuOksyfUQNFjfsOCXkPM=
+cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps=
+cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8=
 cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M=
 cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc=
 cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
@@ -27,10 +27,10 @@ github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0
 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1 h1:1mvYtZfWQAnwNah/C+Z+Jb9rQH95LPE2vlmMuWAHJk8=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1/go.mod h1:75I/mXtme1JyWFtz8GocPHVFyH421IBoZErnO16dd0k=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1 h1:Bk5uOhSAenHyR5P61D/NzeQCv+4fEVV8mOkJ82NqpWw=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1/go.mod h1:QZ4pw3or1WPmRBxf0cHd1tknzrT54WPBOQoGutCPvSU=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2/go.mod h1:SqINnQ9lVVdRlyC8cd1lCI0SdX4n2paeABd2K8ggfnE=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
 github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
 github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
 github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.0 h1:7rKG7UmnrxX4N53TFhkYqjc+kVUZuw0fL8I3Fh+Ld9E=
@@ -64,8 +64,8 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM
 github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
 github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
 github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
-github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ=
-github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
+github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 h1:H5xDQaE3XowWfhZRUpnfC+rGZMEVoSiji+b+/HFAPU4=
+github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
 github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
@@ -128,36 +128,36 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d
 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
 github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk=
 github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
-github.com/aws/aws-sdk-go-v2 v1.36.1 h1:iTDl5U6oAhkNPba0e1t1hrwAo02ZMqbrGq4k5JBWM5E=
-github.com/aws/aws-sdk-go-v2 v1.36.1/go.mod h1:5PMILGVKiW32oDzjj6RU52yrNrDPUHcbZQYr1sM7qmM=
-github.com/aws/aws-sdk-go-v2/config v1.29.6 h1:fqgqEKK5HaZVWLQoLiC9Q+xDlSp+1LYidp6ybGE2OGg=
-github.com/aws/aws-sdk-go-v2/config v1.29.6/go.mod h1:Ft+WLODzDQmCTHDvqAH1JfC2xxbZ0MxpZAcJqmE1LTQ=
-github.com/aws/aws-sdk-go-v2/credentials v1.17.59 h1:9btwmrt//Q6JcSdgJOLI98sdr5p7tssS9yAsGe8aKP4=
-github.com/aws/aws-sdk-go-v2/credentials v1.17.59/go.mod h1:NM8fM6ovI3zak23UISdWidyZuI1ghNe2xjzUZAyT+08=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.28 h1:KwsodFKVQTlI5EyhRSugALzsV6mG/SGrdjlMXSZSdso=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.28/go.mod h1:EY3APf9MzygVhKuPXAc5H+MkGb8k/DOSQjWS0LgkKqI=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32 h1:BjUcr3X3K0wZPGFg2bxOWW3VPN8rkE3/61zhP+IHviA=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32/go.mod h1:80+OGC/bgzzFFTUmcuwD0lb4YutwQeKLFpmt6hoWapU=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32 h1:m1GeXHVMJsRsUAqG6HjZWx9dj7F5TR+cF1bjyfYyBd4=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32/go.mod h1:IitoQxGfaKdVLNg0hD8/DXmAqNy0H4K2H2Sf91ti8sI=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 h1:Pg9URiobXy85kgFev3og2CuOZ8JZUBENF+dcgWBaYNk=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
+github.com/aws/aws-sdk-go-v2 v1.36.2 h1:Ub6I4lq/71+tPb/atswvToaLGVMxKZvjYDVOWEExOcU=
+github.com/aws/aws-sdk-go-v2 v1.36.2/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
+github.com/aws/aws-sdk-go-v2/config v1.29.7 h1:71nqi6gUbAUiEQkypHQcNVSFJVUFANpSeUNShiwWX2M=
+github.com/aws/aws-sdk-go-v2/config v1.29.7/go.mod h1:yqJQ3nh2HWw/uxd56bicyvmDW4KSc+4wN6lL8pYjynU=
+github.com/aws/aws-sdk-go-v2/credentials v1.17.60 h1:1dq+ELaT5ogfmqtV1eocq8SpOK1NRsuUfmhQtD/XAh4=
+github.com/aws/aws-sdk-go-v2/credentials v1.17.60/go.mod h1:HDes+fn/xo9VeszXqjBVkxOo/aUy8Mc6QqKvZk32GlE=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.29 h1:JO8pydejFKmGcUNiiwt75dzLHRWthkwApIvPoyUtXEg=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.29/go.mod h1:adxZ9i9DRmB8zAT0pO0yGnsmu0geomp5a3uq5XpgOJ8=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.33 h1:knLyPMw3r3JsU8MFHWctE4/e2qWbPaxDYLlohPvnY8c=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.33/go.mod h1:EBp2HQ3f+XCB+5J+IoEbGhoV7CpJbnrsd4asNXmTL0A=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.33 h1:K0+Ne08zqti8J9jwENxZ5NoUyBnaFDTu3apwQJWrwwA=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.33/go.mod h1:K97stwwzaWzmqxO8yLGHhClbVW1tC6VT1pDLk1pGrq4=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
 github.com/aws/aws-sdk-go-v2/service/ecr v1.40.3 h1:a+210FCU/pR5hhKRaskRfX/ogcyyzFBrehcTk5DTAyU=
 github.com/aws/aws-sdk-go-v2/service/ecr v1.40.3/go.mod h1:dtD3a4sjUjVL86e0NUvaqdGvds5ED6itUiZPDaT+Gh8=
 github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.31.2 h1:E6/Myrj9HgLF22medmDrKmbpm4ULsa+cIBNx3phirBk=
 github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.31.2/go.mod h1:OQ8NALFcchBJ/qruak6zKUQodovnTKKaReTuCkc5/9Y=
-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA=
-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13 h1:SYVGSFQHlchIcy6e7x12bsrxClCXSP5et8cqVhL8cuw=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13/go.mod h1:kizuDaLX37bG5WZaoxGPQR/LNFXpxp0vsUnqfkWXfNE=
-github.com/aws/aws-sdk-go-v2/service/kms v1.37.18 h1:pi9M/9n1PLayBXjia7LfwgXwcpFdFO7Q2cqKOZa1ZmM=
-github.com/aws/aws-sdk-go-v2/service/kms v1.37.18/go.mod h1:vZXvmzfhdsPj/axc8+qk/2fSCP4hGyaZ1MAduWEHAxM=
-github.com/aws/aws-sdk-go-v2/service/sso v1.24.15 h1:/eE3DogBjYlvlbhd2ssWyeuovWunHLxfgw3s/OJa4GQ=
-github.com/aws/aws-sdk-go-v2/service/sso v1.24.15/go.mod h1:2PCJYpi7EKeA5SkStAmZlF6fi0uUABuhtF8ILHjGc3Y=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.14 h1:M/zwXiL2iXUrHputuXgmO94TVNmcenPHxgLXLutodKE=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.14/go.mod h1:RVwIw3y/IqxC2YEXSIkAzRDdEU1iRabDPaYjpGCbCGQ=
-github.com/aws/aws-sdk-go-v2/service/sts v1.33.14 h1:TzeR06UCMUq+KA3bDkujxK1GVGy+G8qQN/QVYzGLkQE=
-github.com/aws/aws-sdk-go-v2/service/sts v1.33.14/go.mod h1:dspXf/oYWGWo6DEvj98wpaTeqt5+DMidZD0A9BYTizc=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.14 h1:2scbY6//jy/s8+5vGrk7l1+UtHl0h9A4MjOO2k/TM2E=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.14/go.mod h1:bRpZPHZpSe5YRHmPfK3h1M7UBFCn2szHzyx0rw04zro=
+github.com/aws/aws-sdk-go-v2/service/kms v1.37.19 h1:QxVwGw8i/uiI9uXWwvS/m76wCJiiEV6xssBTvs3rwTw=
+github.com/aws/aws-sdk-go-v2/service/kms v1.37.19/go.mod h1:Lcpx4mFS+YjFuKvFaS3GM8qSFQIvRmItZEghMD8evRo=
+github.com/aws/aws-sdk-go-v2/service/sso v1.24.16 h1:YV6xIKDJp6U7YB2bxfud9IENO1LRpGhe2Tv/OKtPrOQ=
+github.com/aws/aws-sdk-go-v2/service/sso v1.24.16/go.mod h1:DvbmMKgtpA6OihFJK13gHMZOZrCHttz8wPHGKXqU+3o=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.15 h1:kMyK3aKotq1aTBsj1eS8ERJLjqYRRRcsmP33ozlCvlk=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.15/go.mod h1:5uPZU7vSNzb8Y0dm75xTikinegPYK3uJmIHQZFq5Aqo=
+github.com/aws/aws-sdk-go-v2/service/sts v1.33.15 h1:ht1jVmeeo2anR7zDiYJLSnRYnO/9NILXXu42FP3rJg0=
+github.com/aws/aws-sdk-go-v2/service/sts v1.33.15/go.mod h1:xWZ5cOiFe3czngChE4LhCBqUxNwgfwndEF7XlYP/yD8=
 github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
 github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
 github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.9.1 h1:50sS0RWhGpW/yZx2KcDNEb1u1MANv5BMEkJgcieEDTA=
@@ -167,8 +167,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
 github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
-github.com/buildkite/agent/v3 v3.92.1 h1:6HLdDbU5z6ZyJ3TCt/UQEcLv2nhg/gdS4ApnsrUwhOE=
-github.com/buildkite/agent/v3 v3.92.1/go.mod h1:mUNebi1cYh66iBjqVdJTgEn+sm53x8zC/XQQfpZSk9A=
+github.com/buildkite/agent/v3 v3.93.1 h1:wXSf37suk/w6AStm/wysuoub1lHpNs7V3XK2rv4K9Dw=
+github.com/buildkite/agent/v3 v3.93.1/go.mod h1:L903LqPpX/wIxIIQOLNG6lQswWNbSXiMVqJ8T+mtatg=
 github.com/buildkite/go-pipeline v0.13.3 h1:llI7sAdZ7sqYE7r8ePlmDADRhJ1K0Kua2+gv74Z9+Es=
 github.com/buildkite/go-pipeline v0.13.3/go.mod h1:1uC2XdHkTV1G5jYv9K8omERIwrsYbBruBrPx1Zu1uFw=
 github.com/buildkite/interpolate v0.1.5 h1:v2Ji3voik69UZlbfoqzx+qfcsOKLA61nHdU79VV+tPU=
@@ -278,8 +278,8 @@ github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyN
 github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
 github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
 github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
-github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
-github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
+github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
+github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
 github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
 github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -722,16 +722,16 @@ github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97
 github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk=
 github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
 github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
-gitlab.com/gitlab-org/api/client-go v0.123.0 h1:W3LZ5QNyiSCJA0Zchkwz8nQIUzOuDoSWMZtRDT5DjPI=
-gitlab.com/gitlab-org/api/client-go v0.123.0/go.mod h1:Jh0qjLILEdbO6z/OY94RD+3NDQRUKiuFSFYozN6cpKM=
+gitlab.com/gitlab-org/api/client-go v0.124.0 h1:6i/uAl3QZur0F4S+42d9/k8y1Lf+htPqQ9YgXZJ2oQI=
+gitlab.com/gitlab-org/api/client-go v0.124.0/go.mod h1:Jh0qjLILEdbO6z/OY94RD+3NDQRUKiuFSFYozN6cpKM=
 go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
 go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
 go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
 go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
 go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
 go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
 go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
@@ -909,8 +909,8 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
-google.golang.org/api v0.222.0 h1:Aiewy7BKLCuq6cUCeOUrsAlzjXPqBkEeQ/iwGHVQa/4=
-google.golang.org/api v0.222.0/go.mod h1:efZia3nXpWELrwMlN5vyQrD4GmJN1Vw0x68Et3r+a9c=
+google.golang.org/api v0.223.0 h1:JUTaWEriXmEy5AhvdMgksGGPEFsYfUKaPEYXd4c3Wvc=
+google.golang.org/api v0.223.0/go.mod h1:C+RS7Z+dDwds2b+zoAk5hN/eSfsiCn0UDrYof/M4d2M=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
@@ -919,8 +919,8 @@ google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD
 google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc=
 google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA=
 google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250212204824-5a70512c5d8b h1:FQtJ1MxbXoIIrZHZ33M+w5+dAP9o86rgpjoKr/ZmT7k=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250212204824-5a70512c5d8b/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 h1:DMTIbak9GhdaSxEjvVzAeNZvyc03I61duqNbnm3SU0M=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go
index e565052d..b2c9ee66 100644
--- a/pkg/cosign/verify.go
+++ b/pkg/cosign/verify.go
@@ -38,6 +38,7 @@ import (
 	"github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer"
 	"github.com/digitorus/timestamp"
 	"github.com/go-openapi/runtime"
+
 	"github.com/google/go-containerregistry/pkg/name"
 	v1 "github.com/google/go-containerregistry/pkg/v1"
 	"github.com/google/go-containerregistry/pkg/v1/remote/transport"
@@ -53,6 +54,7 @@ import (
 	ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote"
 	"github.com/sigstore/cosign/v2/pkg/oci/static"
 	"github.com/sigstore/cosign/v2/pkg/types"
+	protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1"
 	"github.com/sigstore/rekor/pkg/generated/client"
 	"github.com/sigstore/rekor/pkg/generated/models"
 	rekor_types "github.com/sigstore/rekor/pkg/types"
@@ -61,6 +63,7 @@ import (
 	intoto_v001 "github.com/sigstore/rekor/pkg/types/intoto/v0.0.1"
 	intoto_v002 "github.com/sigstore/rekor/pkg/types/intoto/v0.0.2"
 	rekord_v001 "github.com/sigstore/rekor/pkg/types/rekord/v0.0.1"
+	sgbundle "github.com/sigstore/sigstore-go/pkg/bundle"
 	"github.com/sigstore/sigstore-go/pkg/fulcio/certificate"
 	"github.com/sigstore/sigstore-go/pkg/root"
 	"github.com/sigstore/sigstore-go/pkg/verify"
@@ -179,11 +182,18 @@ type verifyTrustedMaterial struct {
 }
 
 func (v *verifyTrustedMaterial) PublicKeyVerifier(hint string) (root.TimeConstrainedVerifier, error) {
+	if v.keyTrustedMaterial == nil {
+		return nil, fmt.Errorf("no public key material available")
+	}
 	return v.keyTrustedMaterial.PublicKeyVerifier(hint)
 }
 
 // verificationOptions returns the verification options for verifying with sigstore-go.
 func (co *CheckOpts) verificationOptions() (trustedMaterial root.TrustedMaterial, verifierOptions []verify.VerifierOption, policyOptions []verify.PolicyOption, err error) {
+	if co.TrustedMaterial == nil {
+		return nil, nil, nil, fmt.Errorf("TrustMaterial is required")
+	}
+
 	policyOptions = make([]verify.PolicyOption, 0)
 
 	if len(co.Identities) > 0 {
@@ -579,6 +589,10 @@ func VerifyImageSignatures(ctx context.Context, signedImgRef name.Reference, co
 		}
 	}
 
+	if co.NewBundleFormat {
+		return nil, false, errors.New("bundle support for image signatures is not yet implemented")
+	}
+
 	// Enforce this up front.
 	if co.RootCerts == nil && co.SigVerifier == nil {
 		return nil, false, errors.New("one of verifier or root certs is required")
@@ -967,8 +981,11 @@ func loadSignatureFromFile(ctx context.Context, sigRef string, signedImgRef name
 // If there were no valid attestations, we return an error.
 func VerifyImageAttestations(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) (checkedAttestations []oci.Signature, bundleVerified bool, err error) {
 	// Enforce this up front.
-	if co.RootCerts == nil && co.SigVerifier == nil {
-		return nil, false, errors.New("one of verifier or root certs is required")
+	if co.RootCerts == nil && co.SigVerifier == nil && co.TrustedMaterial == nil {
+		return nil, false, errors.New("one of verifier, root certs, or TrustedMaterial is required")
+	}
+	if co.NewBundleFormat {
+		return verifyImageAttestationsSigstoreBundle(ctx, signedImgRef, co)
 	}
 
 	// This is a carefully optimized sequence for fetching the attestations of
@@ -1500,3 +1517,133 @@ func verifyImageSignaturesExperimentalOCI(ctx context.Context, signedImgRef name
 
 	return verifySignatures(ctx, sigs, h, co)
 }
+
+func getBundles(_ context.Context, signedImgRef name.Reference, co *CheckOpts) ([]*sgbundle.Bundle, *v1.Hash, error) {
+	// This is a carefully optimized sequence for fetching the signatures of the
+	// entity that minimizes registry requests when supplied with a digest input
+	digest, err := ociremote.ResolveDigest(signedImgRef, co.RegistryClientOpts...)
+	if err != nil {
+		if terr := (&transport.Error{}); errors.As(err, &terr) && terr.StatusCode == http.StatusNotFound {
+			return nil, nil, &ErrImageTagNotFound{
+				fmt.Errorf("image tag not found: %w", err),
+			}
+		}
+		return nil, nil, err
+	}
+	h, err := v1.NewHash(digest.Identifier())
+	if err != nil {
+		return nil, nil, err
+	}
+
+	index, err := ociremote.Referrers(digest, "", co.RegistryClientOpts...)
+	if err != nil {
+		return nil, nil, err
+	}
+	var bundles = make([]*sgbundle.Bundle, 0, len(index.Manifests))
+	for _, result := range index.Manifests {
+		st, err := name.ParseReference(fmt.Sprintf("%s@%s", digest.Repository, result.Digest.String()))
+		if err != nil {
+			return nil, nil, err
+		}
+		bundle, err := ociremote.Bundle(st, co.RegistryClientOpts...)
+		if err != nil {
+			// There may be non-Sigstore referrers in the index, so we can ignore them.
+			// TODO: Should we surface any errors here (e.g. if the bundle is invalid)?
+			continue
+		}
+		bundles = append(bundles, bundle)
+	}
+
+	if len(bundles) == 0 {
+		return nil, nil, &ErrNoMatchingAttestations{
+			fmt.Errorf("no valid bundles exist in registry"),
+		}
+	}
+
+	return bundles, &h, nil
+}
+
+// verifyImageAttestationsSigstoreBundle verifies attestations from attached sigstore bundles
+func verifyImageAttestationsSigstoreBundle(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) (checkedAttestations []oci.Signature, atLeastOneBundleVerified bool, err error) {
+	bundles, hash, err := getBundles(ctx, signedImgRef, co)
+	if err != nil {
+		return nil, false, err
+	}
+
+	digestBytes, err := hex.DecodeString(hash.Hex)
+	if err != nil {
+		return nil, false, err
+	}
+
+	artifactPolicyOption := verify.WithArtifactDigest(hash.Algorithm, digestBytes)
+
+	attestations := make([]oci.Signature, len(bundles))
+	bundlesVerified := make([]bool, len(bundles))
+
+	workers := co.MaxWorkers
+	if co.MaxWorkers == 0 {
+		workers = cosign.DefaultMaxWorkers
+	}
+	t := throttler.New(workers, len(bundles))
+	for i, bundle := range bundles {
+		go func(bundle *sgbundle.Bundle, index int) {
+			var att oci.Signature
+			if err := func(bundle *sgbundle.Bundle) error {
+				_, err := VerifyNewBundle(ctx, co, artifactPolicyOption, bundle)
+				if err != nil {
+					return err
+				}
+				dsse, ok := bundle.Content.(*protobundle.Bundle_DsseEnvelope)
+				if !ok {
+					return fmt.Errorf("bundle does not contain a DSSE envelope")
+				}
+				payload, err := json.Marshal(dsse.DsseEnvelope)
+				if err != nil {
+					return fmt.Errorf("marshaling DSSE envelope: %w", err)
+				}
+
+				// We will return a slice of `[]oci.Signature` from this function for compatibility
+				// with the rest of the codebase. To do that, we wrap the verification output in a
+				// `oci.Signature` using static.NewAttestation(). This type may contain additional
+				// data such as the certificate chain, and rekor/tsa data, but for now we only use
+				// the payload (DSSE). TODO: Add additional data to returned `oci.Signature`. This
+				// can be done by passing a list of static.Option to NewAttestation (e.g. static.WithCertChain()).
+				// Depends on https://github.com/sigstore/sigstore-go/issues/328
+				att, err = static.NewAttestation(payload)
+				if err != nil {
+					return err
+				}
+				bundlesVerified[index] = true
+
+				return err
+			}(bundle); err != nil {
+				t.Done(err)
+				return
+			}
+
+			attestations[index] = att
+			t.Done(nil)
+		}(bundle, i)
+
+		// wait till workers are available
+		t.Throttle()
+	}
+
+	for _, a := range attestations {
+		if a != nil {
+			checkedAttestations = append(checkedAttestations, a)
+		}
+	}
+
+	for _, verified := range bundlesVerified {
+		atLeastOneBundleVerified = atLeastOneBundleVerified || verified
+	}
+
+	if len(checkedAttestations) == 0 {
+		return nil, false, &ErrNoMatchingAttestations{
+			fmt.Errorf("no matching attestations: %w", errors.Join(t.Errs()...)),
+		}
+	}
+
+	return checkedAttestations, atLeastOneBundleVerified, nil
+}
diff --git a/pkg/oci/remote/referrers.go b/pkg/oci/remote/referrers.go
index 24cb802e..1b67cdf3 100644
--- a/pkg/oci/remote/referrers.go
+++ b/pkg/oci/remote/referrers.go
@@ -25,7 +25,9 @@ import (
 func Referrers(d name.Digest, artifactType string, opts ...Option) (*v1.IndexManifest, error) {
 	o := makeOptions(name.Repository{}, opts...)
 	rOpt := o.ROpt
-	rOpt = append(rOpt, remote.WithFilter("artifactType", artifactType))
+	if artifactType != "" {
+		rOpt = append(rOpt, remote.WithFilter("artifactType", artifactType))
+	}
 	idx, err := remote.Referrers(d, rOpt...)
 	if err != nil {
 		return nil, err
diff --git a/pkg/oci/remote/signatures.go b/pkg/oci/remote/signatures.go
index 825d8072..c24e3a20 100644
--- a/pkg/oci/remote/signatures.go
+++ b/pkg/oci/remote/signatures.go
@@ -17,7 +17,9 @@ package remote
 
 import (
 	"errors"
+	"io"
 	"net/http"
+	"strings"
 
 	"github.com/google/go-containerregistry/pkg/name"
 	v1 "github.com/google/go-containerregistry/pkg/v1"
@@ -26,6 +28,7 @@ import (
 	"github.com/sigstore/cosign/v2/pkg/oci"
 	"github.com/sigstore/cosign/v2/pkg/oci/empty"
 	"github.com/sigstore/cosign/v2/pkg/oci/internal/signature"
+	sgbundle "github.com/sigstore/sigstore-go/pkg/bundle"
 )
 
 const maxLayers = 1000
@@ -49,6 +52,45 @@ func Signatures(ref name.Reference, opts ...Option) (oci.Signatures, error) {
 	}, nil
 }
 
+func Bundle(ref name.Reference, opts ...Option) (*sgbundle.Bundle, error) {
+	o := makeOptions(ref.Context(), opts...)
+	img, err := remoteImage(ref, o.ROpt...)
+	if err != nil {
+		return nil, err
+	}
+	layers, err := img.Layers()
+	if err != nil {
+		return nil, err
+	}
+	if len(layers) != 1 {
+		return nil, errors.New("expected exactly one layer")
+	}
+	mediaType, err := layers[0].MediaType()
+	if err != nil {
+		return nil, err
+	}
+	if !strings.HasPrefix(string(mediaType), "application/vnd.dev.sigstore.bundle") {
+		return nil, errors.New("expected bundle layer")
+	}
+	layer0, err := layers[0].Uncompressed()
+	if err != nil {
+		return nil, err
+	}
+	bundleBytes, err := io.ReadAll(layer0)
+	if err != nil {
+		return nil, err
+	}
+	b := &sgbundle.Bundle{}
+	err = b.UnmarshalJSON(bundleBytes)
+	if err != nil {
+		return nil, err
+	}
+	if !b.MinVersion("v0.3") {
+		return nil, errors.New("bundle version too old")
+	}
+	return b, nil
+}
+
 type sigs struct {
 	v1.Image
 }
diff --git a/pkg/oci/remote/write.go b/pkg/oci/remote/write.go
index d0614768..d353c6b0 100644
--- a/pkg/oci/remote/write.go
+++ b/pkg/oci/remote/write.go
@@ -20,6 +20,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"os"
+	"time"
 
 	"github.com/google/go-containerregistry/pkg/name"
 	v1 "github.com/google/go-containerregistry/pkg/v1"
@@ -29,6 +30,7 @@ import (
 	ociexperimental "github.com/sigstore/cosign/v2/internal/pkg/oci/remote"
 	"github.com/sigstore/cosign/v2/pkg/oci"
 	ctypes "github.com/sigstore/cosign/v2/pkg/types"
+	sgbundle "github.com/sigstore/sigstore-go/pkg/bundle"
 )
 
 // WriteSignedImageIndexImages writes the images within the image index
@@ -221,3 +223,126 @@ func (taggable taggableManifest) RawManifest() ([]byte, error) {
 func (taggable taggableManifest) MediaType() (types.MediaType, error) {
 	return taggable.mediaType, nil
 }
+
+func WriteAttestationNewBundleFormat(d name.Digest, bundleBytes []byte, predicateType string, opts ...Option) error {
+	o := makeOptions(d.Repository, opts...)
+
+	signTarget := d.String()
+	ref, err := name.ParseReference(signTarget, o.NameOpts...)
+	if err != nil {
+		return err
+	}
+	desc, err := remote.Head(ref, o.ROpt...)
+	if err != nil {
+		return err
+	}
+
+	// Write the empty config layer
+	configLayer := static.NewLayer([]byte("{}"), "application/vnd.oci.image.config.v1+json")
+	configDigest, err := configLayer.Digest()
+	if err != nil {
+		return fmt.Errorf("failed to calculate digest: %w", err)
+	}
+	configSize, err := configLayer.Size()
+	if err != nil {
+		return fmt.Errorf("failed to calculate size: %w", err)
+	}
+	err = remote.WriteLayer(d.Repository, configLayer, o.ROpt...)
+	if err != nil {
+		return fmt.Errorf("failed to upload layer: %w", err)
+	}
+
+	// generate bundle media type string
+	bundleMediaType, err := sgbundle.MediaTypeString("0.3")
+	if err != nil {
+		return fmt.Errorf("failed to generate bundle media type string: %w", err)
+	}
+
+	// Write the bundle layer
+	layer := static.NewLayer(bundleBytes, types.MediaType(bundleMediaType))
+	blobDigest, err := layer.Digest()
+	if err != nil {
+		return fmt.Errorf("failed to calculate digest: %w", err)
+	}
+
+	blobSize, err := layer.Size()
+	if err != nil {
+		return fmt.Errorf("failed to calculate size: %w", err)
+	}
+
+	err = remote.WriteLayer(d.Repository, layer, o.ROpt...)
+	if err != nil {
+		return fmt.Errorf("failed to upload layer: %w", err)
+	}
+
+	// Create a manifest that includes the blob as a layer
+	manifest := referrerManifest{v1.Manifest{
+		SchemaVersion: 2,
+		MediaType:     types.OCIManifestSchema1,
+		Config: v1.Descriptor{
+			MediaType:    types.MediaType("application/vnd.oci.empty.v1+json"),
+			ArtifactType: bundleMediaType,
+			Digest:       configDigest,
+			Size:         configSize,
+		},
+		Layers: []v1.Descriptor{
+			{
+				MediaType: types.MediaType(bundleMediaType),
+				Digest:    blobDigest,
+				Size:      blobSize,
+			},
+		},
+		Subject: &v1.Descriptor{
+			MediaType: desc.MediaType,
+			Digest:    desc.Digest,
+			Size:      desc.Size,
+		},
+		Annotations: map[string]string{
+			"org.opencontainers.image.created":  time.Now().UTC().Format(time.RFC3339),
+			"dev.sigstore.bundle.content":       "dsse-envelope",
+			"dev.sigstore.bundle.predicateType": predicateType,
+		},
+	}, bundleMediaType}
+
+	targetRef, err := manifest.targetRef(d.Repository)
+	if err != nil {
+		return fmt.Errorf("failed to create target reference: %w", err)
+	}
+
+	if err := remote.Put(targetRef, manifest, o.ROpt...); err != nil {
+		return fmt.Errorf("failed to upload manifest: %w", err)
+	}
+
+	return nil
+}
+
+// referrerManifest implements Taggable for use in remote.Put.
+// This type also augments the built-in v1.Manifest with an ArtifactType field
+// which is part of the OCI 1.1 Image Manifest spec but is unsupported by
+// go-containerregistry at this time.
+// See https://github.com/opencontainers/image-spec/blob/v1.1.0/manifest.md#image-manifest-property-descriptions
+// and https://github.com/google/go-containerregistry/pull/1931
+type referrerManifest struct {
+	v1.Manifest
+	ArtifactType string `json:"artifactType,omitempty"`
+}
+
+func (r referrerManifest) RawManifest() ([]byte, error) {
+	return json.Marshal(r)
+}
+
+func (r referrerManifest) targetRef(repo name.Repository) (name.Reference, error) {
+	manifestBytes, err := r.RawManifest()
+	if err != nil {
+		return nil, err
+	}
+	digest, _, err := v1.SHA256(bytes.NewReader(manifestBytes))
+	if err != nil {
+		return nil, err
+	}
+	return name.ParseReference(fmt.Sprintf("%s/%s@%s", repo.RegistryStr(), repo.RepositoryStr(), digest.String()))
+}
+
+func (r referrerManifest) MediaType() (types.MediaType, error) {
+	return types.OCIManifestSchema1, nil
+}
diff --git a/release/cloudbuild.yaml b/release/cloudbuild.yaml
index eff8c8cc..f6162391 100644
--- a/release/cloudbuild.yaml
+++ b/release/cloudbuild.yaml
@@ -32,20 +32,20 @@ steps:
         echo "Checking out ${_GIT_TAG}"
         git checkout ${_GIT_TAG}
 
-  - name: 'ghcr.io/sigstore/cosign/cosign:v2.4.2-dev@sha256:b69af124cb744c22eba955ebaf9514b42b1556811211e1cd744331350ccd815a'
+  - name: 'ghcr.io/sigstore/cosign/cosign:v2.4.3-dev@sha256:ed76c008e733aa64d257f754a02eb07b251525ea8dc08f40974baec317dea8c9'
     dir: "go/src/sigstore/cosign"
     env:
       - TUF_ROOT=/tmp
     args:
       - 'verify'
-      - 'ghcr.io/gythialy/golang-cross:v1.23.6-0@sha256:6e8da612570af388b204b833c0925c669d00f7654cf964e5bc3e808013234c05'
+      - 'ghcr.io/gythialy/golang-cross:v1.24.0-0@sha256:5a528c7ccfb6db79769f79a09120141a6f251ba0660046dac2108bd14eedb82d'
       - '--certificate-oidc-issuer'
       - "https://token.actions.githubusercontent.com"
       - '--certificate-identity'
-      - "https://github.com/gythialy/golang-cross/.github/workflows/release-golang-cross.yml@refs/tags/v1.23.6-0"
+      - "https://github.com/gythialy/golang-cross/.github/workflows/release-golang-cross.yml@refs/tags/v1.24.0-0"
 
   # maybe we can build our own image and use that to be more in a safe side
-  - name: ghcr.io/gythialy/golang-cross:v1.23.6-0@sha256:6e8da612570af388b204b833c0925c669d00f7654cf964e5bc3e808013234c05
+  - name: ghcr.io/gythialy/golang-cross:v1.24.0-0@sha256:5a528c7ccfb6db79769f79a09120141a6f251ba0660046dac2108bd14eedb82d
     entrypoint: /bin/sh
     dir: "go/src/sigstore/cosign"
     env:
@@ -68,7 +68,7 @@ steps:
         gcloud auth configure-docker \
         && make release
 
-  - name: ghcr.io/gythialy/golang-cross:v1.23.6-0@sha256:6e8da612570af388b204b833c0925c669d00f7654cf964e5bc3e808013234c05
+  - name: ghcr.io/gythialy/golang-cross:v1.24.0-0@sha256:5a528c7ccfb6db79769f79a09120141a6f251ba0660046dac2108bd14eedb82d
     entrypoint: 'bash'
     dir: "go/src/sigstore/cosign"
     env:


Test patch:
diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml
index 78820265..9bfbc1e6 100644
--- a/.github/workflows/e2e-tests.yml
+++ b/.github/workflows/e2e-tests.yml
@@ -86,7 +86,7 @@ jobs:
       VAULT_TOKEN: "root"
       VAULT_ADDR: "http://localhost:8200"
       COSIGN_YES: "true"
-      SCAFFOLDING_RELEASE_VERSION: "v0.7.18"
+      SCAFFOLDING_RELEASE_VERSION: "v0.7.21"
     steps:
       - name: Checkout
         uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
@@ -118,7 +118,7 @@ jobs:
     runs-on: ubuntu-latest
 
     env:
-      SCAFFOLDING_RELEASE_VERSION: "v0.7.18"
+      SCAFFOLDING_RELEASE_VERSION: "v0.7.21"
 
     steps:
     - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
diff --git a/.github/workflows/kind-verify-attestation.yaml b/.github/workflows/kind-verify-attestation.yaml
index 7d156390..f405385f 100644
--- a/.github/workflows/kind-verify-attestation.yaml
+++ b/.github/workflows/kind-verify-attestation.yaml
@@ -46,7 +46,7 @@ jobs:
 
     env:
       KO_DOCKER_REPO: "registry.local:5000/policy-controller"
-      SCAFFOLDING_RELEASE_VERSION: "v0.7.18"
+      SCAFFOLDING_RELEASE_VERSION: "v0.7.21"
       GO111MODULE: on
       GOFLAGS: -ldflags=-s -ldflags=-w
       KOCACHE: ~/ko
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index 91fec7f7..2e42ccb0 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -45,7 +45,7 @@ jobs:
         with:
           persist-credentials: false
       # https://github.com/mvdan/github-actions-golang#how-do-i-set-up-caching-between-builds
-      - uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
+      - uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
         with:
           # In order:
           # * Module download cache
@@ -70,7 +70,7 @@ jobs:
           GODEBUG: x509sha1=1
         run: go test -tags=sct -covermode atomic -coverprofile coverage.txt $(go list ./... | grep -v third_party/)
       - name: Upload Coverage Report
-        uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5.3.1
+        uses: codecov/codecov-action@0565863a31f2c772f9f0395002a31e3f06189574 # v5.4.0
         with:
           env_vars: OS
       - name: Run Go tests w/ `-race`
@@ -144,7 +144,7 @@ jobs:
       - name: check disk space
         run: df -h
       # https://github.com/mvdan/github-actions-golang#how-do-i-set-up-caching-between-builds
-      - uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
+      - uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
         with:
           # In order:
           # * Module download cache
@@ -192,7 +192,7 @@ jobs:
           check-latest: true
 
       # https://github.com/mvdan/github-actions-golang#how-do-i-set-up-caching-between-builds
-      - uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
+      - uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
         with:
           # In order:
           # * Module download cache
diff --git a/cmd/cosign/cli/attest.go b/cmd/cosign/cli/attest.go
index 63e8d4b3..8681e41a 100644
--- a/cmd/cosign/cli/attest.go
+++ b/cmd/cosign/cli/attest.go
@@ -91,6 +91,7 @@ func Attest() *cobra.Command {
 				TSAClientCert:            o.TSAClientCert,
 				TSAServerName:            o.TSAServerName,
 				TSAServerURL:             o.TSAServerURL,
+				NewBundleFormat:          o.NewBundleFormat,
 			}
 			attestCommand := attest.AttestCommand{
 				KeyOpts:                 ko,
diff --git a/cmd/cosign/cli/attest/attest.go b/cmd/cosign/cli/attest/attest.go
index f468c043..6a844372 100644
--- a/cmd/cosign/cli/attest/attest.go
+++ b/cmd/cosign/cli/attest/attest.go
@@ -49,7 +49,7 @@ import (
 
 type tlogUploadFn func(*client.Rekor, []byte) (*models.LogEntryAnon, error)
 
-func uploadToTlog(ctx context.Context, sv *sign.SignerVerifier, rekorURL string, upload tlogUploadFn) (*cbundle.RekorBundle, error) {
+func uploadToTlog(ctx context.Context, sv *sign.SignerVerifier, rekorURL string, upload tlogUploadFn) (*models.LogEntryAnon, error) {
 	rekorBytes, err := sv.Bytes(ctx)
 	if err != nil {
 		return nil, err
@@ -64,7 +64,7 @@ func uploadToTlog(ctx context.Context, sv *sign.SignerVerifier, rekorURL string,
 		return nil, err
 	}
 	fmt.Fprintln(os.Stderr, "tlog entry created with index:", *entry.LogIndex)
-	return cbundle.EntryToBundle(entry), nil
+	return entry, nil
 }
 
 // nolint
@@ -174,15 +174,23 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error {
 	if sv.Cert != nil {
 		opts = append(opts, static.WithCertChain(sv.Cert, sv.Chain))
 	}
+	var timestampBytes []byte
+	var tsaPayload []byte
 	if c.KeyOpts.TSAServerURL != "" {
-		// TODO - change this when we implement protobuf / new bundle support
+		// We need to decide what signature to send to the timestamp authority.
 		//
-		// Historically, cosign sent the entire JSON DSSE Envelope to the
-		// timestamp authority. However, when sigstore clients are verifying a
-		// bundle they will use the DSSE Sig field, so we choose what signature
-		// to send to the timestamp authority based on our output format.
-		//
-		// See cmd/cosign/cli/attest/attest_blob.go
+		// Historically, cosign sent `signedPayload`, which is the entire JSON DSSE
+		// Envelope. However, when sigstore clients are verifying a bundle they
+		// will use the DSSE Sig field, so we choose what signature to send to
+		// the timestamp authority based on our output format.
+		if c.KeyOpts.NewBundleFormat {
+			tsaPayload, err = getEnvelopeSigBytes(signedPayload)
+			if err != nil {
+				return err
+			}
+		} else {
+			tsaPayload = signedPayload
+		}
 		tc := tsaclient.NewTSAClient(c.KeyOpts.TSAServerURL)
 		if c.KeyOpts.TSAClientCert != "" {
 			tc = tsaclient.NewTSAClientMTLS(c.KeyOpts.TSAServerURL,
@@ -192,11 +200,11 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error {
 				c.KeyOpts.TSAServerName,
 			)
 		}
-		responseBytes, err := tsa.GetTimestampedSignature(signedPayload, tc)
+		timestampBytes, err = tsa.GetTimestampedSignature(tsaPayload, tc)
 		if err != nil {
 			return err
 		}
-		bundle := cbundle.TimestampToRFC3161Timestamp(responseBytes)
+		bundle := cbundle.TimestampToRFC3161Timestamp(timestampBytes)
 
 		opts = append(opts, static.WithRFC3161Timestamp(bundle))
 	}
@@ -217,8 +225,9 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error {
 	if err != nil {
 		return fmt.Errorf("should upload to tlog: %w", err)
 	}
+	var rekorEntry *models.LogEntryAnon
 	if shouldUpload {
-		bundle, err := uploadToTlog(ctx, sv, c.RekorURL, func(r *client.Rekor, b []byte) (*models.LogEntryAnon, error) {
+		rekorEntry, err = uploadToTlog(ctx, sv, c.RekorURL, func(r *client.Rekor, b []byte) (*models.LogEntryAnon, error) {
 			if c.RekorEntryType == "intoto" {
 				return cosign.TLogUploadInTotoAttestation(ctx, r, signedPayload, b)
 			} else {
@@ -229,7 +238,7 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error {
 		if err != nil {
 			return err
 		}
-		opts = append(opts, static.WithBundle(bundle))
+		opts = append(opts, static.WithBundle(cbundle.EntryToBundle(rekorEntry)))
 	}
 
 	sig, err := static.NewAttestation(signedPayload, opts...)
@@ -237,6 +246,18 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error {
 		return err
 	}
 
+	if c.KeyOpts.NewBundleFormat {
+		signerBytes, err := sv.Bytes(ctx)
+		if err != nil {
+			return err
+		}
+		bundleBytes, err := makeNewBundle(sv, rekorEntry, payload, signedPayload, signerBytes, timestampBytes)
+		if err != nil {
+			return err
+		}
+		return ociremote.WriteAttestationNewBundleFormat(digest, bundleBytes, predicateType, ociremoteOpts...)
+	}
+
 	// We don't actually need to access the remote entity to attach things to it
 	// so we use a placeholder here.
 	se := ociremote.SignedUnknown(digest, ociremoteOpts...)
diff --git a/cmd/cosign/cli/attest/attest_blob.go b/cmd/cosign/cli/attest/attest_blob.go
index 528da791..8b8d2a3e 100644
--- a/cmd/cosign/cli/attest/attest_blob.go
+++ b/cmd/cosign/cli/attest/attest_blob.go
@@ -163,6 +163,7 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
 
 	var rfc3161Timestamp *cbundle.RFC3161Timestamp
 	var timestampBytes []byte
+	var tsaPayload []byte
 	var rekorEntry *models.LogEntryAnon
 
 	if c.KeyOpts.TSAServerURL != "" {
@@ -182,28 +183,16 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
 		// will use the DSSE Sig field, so we choose what signature to send to
 		// the timestamp authority based on our output format.
 		if c.NewBundleFormat {
-			var envelope dsse.Envelope
-			err = json.Unmarshal(sig, &envelope)
-			if err != nil {
-				return err
-			}
-			if len(envelope.Signatures) == 0 {
-				return fmt.Errorf("envelope has no signatures")
-			}
-			envelopeSigBytes, err := base64.StdEncoding.DecodeString(envelope.Signatures[0].Sig)
-			if err != nil {
-				return err
-			}
-
-			timestampBytes, err = tsa.GetTimestampedSignature(envelopeSigBytes, tc)
+			tsaPayload, err = getEnvelopeSigBytes(sig)
 			if err != nil {
 				return err
 			}
 		} else {
-			timestampBytes, err = tsa.GetTimestampedSignature(sig, tc)
-			if err != nil {
-				return err
-			}
+			tsaPayload = sig
+		}
+		timestampBytes, err = tsa.GetTimestampedSignature(tsaPayload, tc)
+		if err != nil {
+			return err
 		}
 		rfc3161Timestamp = cbundle.TimestampToRFC3161Timestamp(timestampBytes)
 		// TODO: Consider uploading RFC3161 TS to Rekor
diff --git a/cmd/cosign/cli/attest/common.go b/cmd/cosign/cli/attest/common.go
index b9bb6dce..e5f4589a 100644
--- a/cmd/cosign/cli/attest/common.go
+++ b/cmd/cosign/cli/attest/common.go
@@ -15,9 +15,13 @@
 package attest
 
 import (
+	"encoding/base64"
+	"encoding/json"
 	"fmt"
 	"io"
 	"os"
+
+	"github.com/secure-systems-lab/go-securesystemslib/dsse"
 )
 
 func predicateReader(predicatePath string) (io.ReadCloser, error) {
@@ -33,3 +37,15 @@ func predicateReader(predicatePath string) (io.ReadCloser, error) {
 	}
 	return f, nil
 }
+
+func getEnvelopeSigBytes(envelopeBytes []byte) ([]byte, error) {
+	var envelope dsse.Envelope
+	err := json.Unmarshal(envelopeBytes, &envelope)
+	if err != nil {
+		return nil, err
+	}
+	if len(envelope.Signatures) == 0 {
+		return nil, fmt.Errorf("envelope has no signatures")
+	}
+	return base64.StdEncoding.DecodeString(envelope.Signatures[0].Sig)
+}
diff --git a/cmd/cosign/cli/options/attest.go b/cmd/cosign/cli/options/attest.go
index 4602629b..6a4bc702 100644
--- a/cmd/cosign/cli/options/attest.go
+++ b/cmd/cosign/cli/options/attest.go
@@ -36,6 +36,7 @@ type AttestOptions struct {
 	TSAServerURL            string
 	RekorEntryType          string
 	RecordCreationTimestamp bool
+	NewBundleFormat         bool
 
 	Rekor       RekorOptions
 	Fulcio      FulcioOptions
@@ -106,4 +107,6 @@ func (o *AttestOptions) AddFlags(cmd *cobra.Command) {
 
 	cmd.Flags().BoolVar(&o.RecordCreationTimestamp, "record-creation-timestamp", false,
 		"set the createdAt timestamp in the attestation artifact to the time it was created; by default, cosign sets this to the zero value")
+
+	cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", false, "attach a Sigstore bundle using OCI referrers API")
 }
diff --git a/cmd/cosign/cli/verify/verify_attestation.go b/cmd/cosign/cli/verify/verify_attestation.go
index bf25cbb4..935d5036 100644
--- a/cmd/cosign/cli/verify/verify_attestation.go
+++ b/cmd/cosign/cli/verify/verify_attestation.go
@@ -37,6 +37,7 @@ import (
 	"github.com/sigstore/cosign/v2/pkg/oci"
 	"github.com/sigstore/cosign/v2/pkg/policy"
 	sigs "github.com/sigstore/cosign/v2/pkg/signature"
+	"github.com/sigstore/sigstore-go/pkg/root"
 )
 
 // VerifyAttestationCommand verifies a signature on a supplied container image
@@ -44,6 +45,7 @@ import (
 type VerifyAttestationCommand struct {
 	options.RegistryOptions
 	options.CertVerifyOptions
+	options.CommonVerifyOptions
 	CheckClaims                  bool
 	KeyRef                       string
 	CertRef                      string
@@ -109,12 +111,24 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
 		IgnoreTlog:                   c.IgnoreTlog,
 		MaxWorkers:                   c.MaxWorkers,
 		UseSignedTimestamps:          c.TSACertChainPath != "" || c.UseSignedTimestamps,
+		NewBundleFormat:              c.NewBundleFormat,
 	}
 	if c.CheckClaims {
 		co.ClaimVerifier = cosign.IntotoSubjectClaimVerifier
 	}
+
+	if c.NewBundleFormat {
+		if err = checkSigstoreBundleUnsupportedOptions(c); err != nil {
+			return err
+		}
+		co.TrustedMaterial, err = loadTrustedRoot(ctx, c.TrustedRootPath)
+		if err != nil {
+			return err
+		}
+	}
+
 	// Ignore Signed Certificate Timestamp if the flag is set or a key is provided
-	if shouldVerifySCT(c.IgnoreSCT, c.KeyRef, c.Sk) {
+	if shouldVerifySCT(c.IgnoreSCT, c.KeyRef, c.Sk) && !c.NewBundleFormat {
 		co.CTLogPubKeys, err = cosign.GetCTLogPubs(ctx)
 		if err != nil {
 			return fmt.Errorf("getting ctlog public keys: %w", err)
@@ -122,7 +136,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
 	}
 
 	// If we are using signed timestamps, we need to load the TSA certificates
-	if co.UseSignedTimestamps {
+	if co.UseSignedTimestamps && !c.NewBundleFormat {
 		tsaCertificates, err := cosign.GetTSACerts(ctx, c.TSACertChainPath, cosign.GetTufTargets)
 		if err != nil {
 			return fmt.Errorf("unable to load TSA certificates: %w", err)
@@ -132,7 +146,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
 		co.TSAIntermediateCertificates = tsaCertificates.IntermediateCerts
 	}
 
-	if !c.IgnoreTlog {
+	if !c.IgnoreTlog && !co.NewBundleFormat {
 		if c.RekorURL != "" {
 			rekorClient, err := rekor.NewClient(c.RekorURL)
 			if err != nil {
@@ -178,6 +192,10 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
 			return fmt.Errorf("initializing piv token verifier: %w", err)
 		}
 	case c.CertRef != "":
+		if c.NewBundleFormat {
+			// This shouldn't happen because we already checked for this above in checkSigstoreBundleUnsupportedOptions
+			return fmt.Errorf("unsupported: certificate reference currently not supported with --new-bundle-format")
+		}
 		cert, err := loadCertFromFileOrURL(c.CertRef)
 		if err != nil {
 			return fmt.Errorf("loading certificate from reference: %w", err)
@@ -214,6 +232,17 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
 			}
 			co.SCT = sct
 		}
+	case c.TrustedRootPath != "":
+		if !c.NewBundleFormat {
+			return fmt.Errorf("unsupported: trusted root path currently only supported with --new-bundle-format")
+		}
+
+		// If a trusted root path is provided, we will use it to verify the bundle.
+		// Otherwise, the verifier will default to the public good instance.
+		co.TrustedMaterial, err = root.NewTrustedRootFromPath(c.TrustedRootPath)
+		if err != nil {
+			return fmt.Errorf("creating trusted root from path: %w", err)
+		}
 	case c.CARoots != "":
 		// CA roots + possible intermediates are already loaded into co.RootCerts with the call to
 		// loadCertsKeylessVerification above.
@@ -318,3 +347,19 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
 
 	return nil
 }
+
+func checkSigstoreBundleUnsupportedOptions(c *VerifyAttestationCommand) error {
+	if c.CertRef != "" {
+		return fmt.Errorf("unsupported: certificate may not be provided using --certificate when using --new-bundle-format (cert must be in bundle)")
+	}
+	if c.CertChain != "" {
+		return fmt.Errorf("unsupported: certificate chain may not be provided using --certificate-chain when using --new-bundle-format (cert must be in bundle)")
+	}
+	if c.CARoots != "" || c.CAIntermediates != "" {
+		return fmt.Errorf("unsupported: CA roots/intermediates must be provided using --trusted-root when using --new-bundle-format")
+	}
+	if c.TSACertChainPath != "" {
+		return fmt.Errorf("unsupported: TSA certificate chain path may only be provided using --trusted-root when using --new-bundle-format")
+	}
+	return nil
+}
diff --git a/doc/cosign_attest.md b/doc/cosign_attest.md
index 2a1171c3..1f647e83 100644
--- a/doc/cosign_attest.md
+++ b/doc/cosign_attest.md
@@ -57,6 +57,7 @@ cosign attest [flags]
       --insecure-skip-verify                                                                     skip verifying fulcio published to the SCT (this should only be used for testing).
       --k8s-keychain                                                                             whether to use the kubernetes keychain instead of the default keychain (supports workload identity).
       --key string                                                                               path to the private key file, KMS URI or Kubernetes Secret
+      --new-bundle-format                                                                        attach a Sigstore bundle using OCI referrers API
       --no-upload                                                                                do not upload the generated attestation
       --oidc-client-id string                                                                    OIDC client ID for application (default "sigstore")
       --oidc-client-secret-file string                                                           Path to file containing OIDC client secret for application
diff --git a/doc/cosign_verify-attestation.md b/doc/cosign_verify-attestation.md
index d370f05c..b2990564 100644
--- a/doc/cosign_verify-attestation.md
+++ b/doc/cosign_verify-attestation.md
@@ -81,6 +81,7 @@ cosign verify-attestation [flags]
       --key string                                                                               path to the public key file, KMS URI or Kubernetes Secret
       --local-image                                                                              whether the specified image is a path to an image saved locally via 'cosign save'
       --max-workers int                                                                          the amount of maximum workers for parallel executions (default 10)
+      --new-bundle-format                                                                        expect the signature/attestation to be packaged in a Sigstore bundle
       --offline                                                                                  only allow offline verification
   -o, --output string                                                                            output format for the signing image information (json|text) (default "json")
       --policy strings                                                                           specify CUE or Rego files with policies to be used for validation
@@ -97,8 +98,9 @@ cosign verify-attestation [flags]
       --sk                                                                                       whether to use a hardware security key
       --slot string                                                                              security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management)
       --timestamp-certificate-chain string                                                       path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA certificate. Optionally may contain intermediate CA certificates, and may contain the leaf TSA certificate if not present in the timestamp
+      --trusted-root string                                                                      Path to a Sigstore TrustedRoot JSON file. Requires --new-bundle-format to be set.
       --type string                                                                              specify a predicate type (slsaprovenance|slsaprovenance02|slsaprovenance1|link|spdx|spdxjson|cyclonedx|vuln|openvex|custom) or an URI (default "custom")
-      --use-signed-timestamps                                                                    use signed timestamps if available
+      --use-signed-timestamps                                                                    verify rfc3161 timestamps
 ```
 
 ### Options inherited from parent commands
diff --git a/doc/cosign_verify-blob-attestation.md b/doc/cosign_verify-blob-attestation.md
index a7c29335..948eaf45 100644
--- a/doc/cosign_verify-blob-attestation.md
+++ b/doc/cosign_verify-blob-attestation.md
@@ -49,7 +49,7 @@ cosign verify-blob-attestation [flags]
       --insecure-ignore-tlog                            ignore transparency log verification, to be used when an artifact signature has not been uploaded to the transparency log. Artifacts cannot be publicly verified when not included in a log
       --key string                                      path to the public key file, KMS URI or Kubernetes Secret
       --max-workers int                                 the amount of maximum workers for parallel executions (default 10)
-      --new-bundle-format                               output bundle in new format that contains all verification material
+      --new-bundle-format                               expect the signature/attestation to be packaged in a Sigstore bundle
       --offline                                         only allow offline verification
       --private-infrastructure                          skip transparency log verification when verifying artifacts in a privately deployed infrastructure
       --rekor-url string                                address of rekor STL server (default "https://rekor.sigstore.dev")
@@ -59,9 +59,9 @@ cosign verify-blob-attestation [flags]
       --sk                                              whether to use a hardware security key
       --slot string                                     security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management)
       --timestamp-certificate-chain string              path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA certificate. Optionally may contain intermediate CA certificates, and may contain the leaf TSA certificate if not present in the timestamp
-      --trusted-root string                             path to trusted root FILE
+      --trusted-root string                             Path to a Sigstore TrustedRoot JSON file. Requires --new-bundle-format to be set.
       --type string                                     specify a predicate type (slsaprovenance|slsaprovenance02|slsaprovenance1|link|spdx|spdxjson|cyclonedx|vuln|openvex|custom) or an URI (default "custom")
-      --use-signed-timestamps                           use signed timestamps if available
+      --use-signed-timestamps                           verify rfc3161 timestamps
 ```
 
 ### Options inherited from parent commands
diff --git a/pkg/cosign/testdata/oci-attestation.sigstore.json b/pkg/cosign/testdata/oci-attestation.sigstore.json
new file mode 100644
index 00000000..c3b65b2a
--- /dev/null
+++ b/pkg/cosign/testdata/oci-attestation.sigstore.json
@@ -0,0 +1,1 @@
+{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json","verificationMaterial":{"certificate":{"rawBytes":"MIICzzCCAlWgAwIBAgIUF2e+Ci0YsLnPaFUknbNDtUcqXl0wCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwMjI4MTkxODExWhcNMjUwMjI4MTkyODExWjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEk/CiYRmA9zsa7ams/+fI+wGeXOFxng4J0wSj1jub7kxArHhGwvgcVE3ubrGXNQB9X3ksG0F+MiXHG8gRzxrr0KOCAXQwggFwMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUcPdotYluBHI4fkYy3yij5SY4728wHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wHgYDVR0RAQH/BBQwEoEQY29keUBzb3lsYW5kLmNvbTAsBgorBgEEAYO/MAEBBB5odHRwczovL2dpdGh1Yi5jb20vbG9naW4vb2F1dGgwLgYKKwYBBAGDvzABCAQgDB5odHRwczovL2dpdGh1Yi5jb20vbG9naW4vb2F1dGgwgYoGCisGAQQB1nkCBAIEfAR6AHgAdgDdPTBqxscRMmMZHhyZZzcCokpeuN48rf+HinKALynujgAAAZVN/q3JAAAEAwBHMEUCIQCdLrw1b9rE6rFd+NAJVPaw4DmH0YNOH2AYIOc4g26WmgIgG7Gub5hz7gzFpvhdb9HQsObWfTNcUiSOmrnPlmaQaKUwCgYIKoZIzj0EAwMDaAAwZQIxAMwB66cCKVhOYq2JMx20FPfJXD6K6oaUV8aIlCiW/YLLZdkWjTWkXt4qIbPgUeAl6wIwdCmnnSMw9fG7FuKz14RYFSbE1VLhNiWhGYpfCj00FtNRoXF+NFQ7DOKjjq7xPjza"},"tlogEntries":[{"logIndex":"175508996","logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="},"kindVersion":{"kind":"dsse","version":"0.0.1"},"integratedTime":"1740770291","inclusionPromise":{"signedEntryTimestamp":"MEUCIQDIgcWm5sXPh52rA/3BLyvfymD4RLEze5cx0WrDCF7tXAIgDtDq9wgYo8Z6RQ/LIaMRY/clocjCbX1qZTI9EVrGbzQ="},"inclusionProof":{"logIndex":"53604734","rootHash":"nd/V1IEweXlu0reIQGaGK1d51xW4Rj8fUyTrmcUkrMY=","treeSize":"53604735","hashes":["p7adBuLwiHyxFA7sjjx9Jt+YJ9NJ6pPRsqrlFv/Prl4=","V8kTVhZDHU0oMb8SoiG+IJzfi1+zs8k07t7msfdk3DU=","Y9K/VuEpG5hexjkO0eGf5aWgfD1no2I/HmjLL79pGOw=","49xFd2wqEUENt7vh3yabkMXtEu6nNJxA6xJDF35HkvQ=","fldHlNczrXBcOY4UnkC1im8c3AZGOojtqHIrnWG81FA=","9JKlhk57UmiGCygvf555Z0loJkdvt8BLIwGeMAuJM1o=","JuKjSUhL4pgP2+mTbT/X01oQa16n9Nh2ruwSgN9Ft3o=","6g/GERajYnzZjZ0EXQIVHpGIKry1LMG0+RJhvtcbSwI=","62MSvX4iwxK6VPg9s7NR+Y1jiYkd/SsZpVoaUYjD8wg=","K7s/kEEUl/59VblorlWa2aJO8TuH5uy8kn/7qSY+T94=","GlND1TZKB9H1XkH8gmyEmNId6gWGYnXjA0ykMIJaI44=","315PrxqKtUW8bpqBdn6SMyj+smOwK7jCa7Px7i4AIOQ=","lL6jxdDTg23iUvuwRop1833jkmWSvr7sLBM3hXZ8tTU=","eExzddanJoxYTKoErFFSTDFUR3UvwaaXxcddWwQJd7I=","ebCKJ53lKWPqIx8mXXgznF9DGoQv70J7JTlFAav6s5E=","vemyaMj0Na1LMjbB/9Dmkq8T+jAb3o+yCESgAayUABU="],"checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n53604735\nnd/V1IEweXlu0reIQGaGK1d51xW4Rj8fUyTrmcUkrMY=\n\n— rekor.sigstore.dev wNI9ajBFAiEAuh6liXP/rBKWjuO/W8jooM66eVHSXo3Cf8kEAIryZLACIAfCuhLOHpSnU3WA68gZci57o2JuwX7S5H6ANbGePH58\n"}},"canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiYzZhZGFhZjE0OTA4NGYxOTc3YzE0YjViMTQxMmY4ODI1OGU1ODMwZDg3YTVjNjQ0MDNjNDAzYjliNzNmZjEzNiJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjE1NjkyNjE2ZTljNDM1OTA3YzQxNDExNDY2ZTQ5M2YxOTVhMzljYTY4Yzk3M2E0ZjUzOTBkZjNkMzAxMzk4YmQifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVRQ0lEb21pdVlWZ05LeWZZei9sZXF2dEVZMnh1aWZ0ZWlnanErTGZTaHU5MkJCQWlCeGl4c0daTllCVlJoR2pYNk9aaVBRQklvVWYraThWZ1BoLy9BVC96NFZnZz09IiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VONmVrTkRRV3hYWjBGM1NVSkJaMGxWUmpKbEswTnBNRmx6VEc1UVlVWlZhMjVpVGtSMFZXTnhXR3d3ZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwVmQwMXFTVFJOVkd0NFQwUkZlRmRvWTA1TmFsVjNUV3BKTkUxVWEzbFBSRVY0VjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVnJMME5wV1ZKdFFUbDZjMkUzWVcxekx5dG1TU3QzUjJWWVQwWjRibWMwU2pCM1Uyb0tNV3AxWWpkcmVFRnlTR2hIZDNablkxWkZNM1ZpY2tkWVRsRkNPVmd6YTNOSE1FWXJUV2xZU0VjNFoxSjZlSEp5TUV0UFEwRllVWGRuWjBaM1RVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVmpVR1J2Q25SWmJIVkNTRWswWm10WmVUTjVhV28xVTFrME56STRkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMGhuV1VSV1VqQlNRVkZJTDBKQ1VYZEZiMFZSV1RJNWEyVlZRbnBpTTJ4eldWYzFhMHh0VG5aaVZFRnpRbWR2Y2tKblJVVkJXVTh2VFVGRlFncENRalZ2WkVoU2QyTjZiM1pNTW1Sd1pFZG9NVmxwTldwaU1qQjJZa2M1Ym1GWE5IWmlNa1l4WkVkbmQweG5XVXRMZDFsQ1FrRkhSSFo2UVVKRFFWRm5Da1JDTlc5a1NGSjNZM3B2ZGt3eVpIQmtSMmd4V1drMWFtSXlNSFppUnpsdVlWYzBkbUl5UmpGa1IyZDNaMWx2UjBOcGMwZEJVVkZDTVc1clEwSkJTVVVLWmtGU05rRklaMEZrWjBSa1VGUkNjWGh6WTFKTmJVMWFTR2g1V2xwNlkwTnZhM0JsZFU0ME9ISm1LMGhwYmt0QlRIbHVkV3BuUVVGQldsWk9MM0V6U2dwQlFVRkZRWGRDU0UxRlZVTkpVVU5rVEhKM01XSTVja1UyY2taa0swNUJTbFpRWVhjMFJHMUlNRmxPVDBneVFWbEpUMk0wWnpJMlYyMW5TV2RITjBkMUNtSTFhSG8zWjNwR2NIWm9aR0k1U0ZGelQySlhabFJPWTFWcFUwOXRjbTVRYkcxaFVXRkxWWGREWjFsSlMyOWFTWHBxTUVWQmQwMUVZVUZCZDFwUlNYZ0tRVTEzUWpZMlkwTkxWbWhQV1hFeVNrMTRNakJHVUdaS1dFUTJTelp2WVZWV09HRkpiRU5wVnk5WlRFeGFaR3RYYWxSWGExaDBOSEZKWWxCblZXVkJiQW8yZDBsM1pFTnRibTVUVFhjNVprYzNSblZMZWpFMFVsbEdVMkpGTVZaTWFFNXBWMmhIV1hCbVEyb3dNRVowVGxKdldFWXJUa1pSTjBSUFMycHFjVGQ0Q2xCcWVtRUtMUzB0TFMxRlRrUWdRMFZTVkVsR1NVTkJWRVV0TFMwdExRbz0ifV19fQ=="}]},"dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2Nvc2lnbi5zaWdzdG9yZS5kZXYvYXR0ZXN0YXRpb24vdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiMTI3LjAuMC4xOjYwOTQzL3JlcG8iLCJkaWdlc3QiOnsic2hhMjU2IjoiNzMyMTEyMjcwZDdlNTk0MThhOGMwODBiMTM0YjI0Y2FiZDY3ZDI1MGQwZDAxNDdhOTdlZDk1YmE1YzI4MGFhNCJ9fV0sInByZWRpY2F0ZSI6eyJEYXRhIjoie1wiZm9vXCI6IFwiYmFyXCJ9XG4iLCJUaW1lc3RhbXAiOiIyMDI1LTAyLTI4VDE5OjE4OjExWiJ9fQ==","payloadType":"application/vnd.in-toto+json","signatures":[{"sig":"MEQCIDomiuYVgNKyfYz/leqvtEY2xuifteigjq+LfShu92BBAiBxixsGZNYBVRhGjX6OZiPQBIoUf+i8VgPh//AT/z4Vgg=="}]}}
diff --git a/pkg/cosign/testdata/trusted_root_pgi.json b/pkg/cosign/testdata/trusted_root_pgi.json
new file mode 100644
index 00000000..b8706cb3
--- /dev/null
+++ b/pkg/cosign/testdata/trusted_root_pgi.json
@@ -0,0 +1,90 @@
+{
+  "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1",
+  "tlogs": [
+    {
+      "baseUrl": "https://rekor.sigstore.dev",
+      "hashAlgorithm": "SHA2_256",
+      "publicKey": {
+        "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwrkBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw==",
+        "keyDetails": "PKIX_ECDSA_P256_SHA_256",
+        "validFor": {
+          "start": "2021-01-12T11:53:27.000Z"
+        }
+      },
+      "logId": {
+        "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="
+      }
+    }
+  ],
+  "certificateAuthorities": [
+    {
+      "subject": {
+        "organization": "sigstore.dev",
+        "commonName": "sigstore"
+      },
+      "uri": "https://fulcio.sigstore.dev",
+      "certChain": {
+        "certificates": [
+          {
+            "rawBytes": "MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAqMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIxMDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSyA7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0JcastaRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6NmMGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2uSu1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJxVe/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uupHr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ=="
+          }
+        ]
+      },
+      "validFor": {
+        "start": "2021-03-07T03:20:29.000Z",
+        "end": "2022-12-31T23:59:59.999Z"
+      }
+    },
+    {
+      "subject": {
+        "organization": "sigstore.dev",
+        "commonName": "sigstore"
+      },
+      "uri": "https://fulcio.sigstore.dev",
+      "certChain": {
+        "certificates": [
+          {
+            "rawBytes": "MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow="
+          },
+          {
+            "rawBytes": "MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ"
+          }
+        ]
+      },
+      "validFor": {
+        "start": "2022-04-13T20:06:15.000Z"
+      }
+    }
+  ],
+  "ctlogs": [
+    {
+      "baseUrl": "https://ctfe.sigstore.dev/test",
+      "hashAlgorithm": "SHA2_256",
+      "publicKey": {
+        "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbfwR+RJudXscgRBRpKX1XFDy3PyudDxz/SfnRi1fT8ekpfBd2O1uoz7jr3Z8nKzxA69EUQ+eFCFI3zeubPWU7w==",
+        "keyDetails": "PKIX_ECDSA_P256_SHA_256",
+        "validFor": {
+          "start": "2021-03-14T00:00:00.000Z",
+          "end": "2022-10-31T23:59:59.999Z"
+        }
+      },
+      "logId": {
+        "keyId": "CGCS8ChS/2hF0dFrJ4ScRWcYrBY9wzjSbea8IgY2b3I="
+      }
+    },
+    {
+      "baseUrl": "https://ctfe.sigstore.dev/2022",
+      "hashAlgorithm": "SHA2_256",
+      "publicKey": {
+        "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiPSlFi0CmFTfEjCUqF9HuCEcYXNKAaYalIJmBZ8yyezPjTqhxrKBpMnaocVtLJBI1eM3uXnQzQGAJdJ4gs9Fyw==",
+        "keyDetails": "PKIX_ECDSA_P256_SHA_256",
+        "validFor": {
+          "start": "2022-10-20T00:00:00.000Z"
+        }
+      },
+      "logId": {
+        "keyId": "3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4="
+      }
+    }
+  ]
+}
diff --git a/pkg/cosign/verify_oci_test.go b/pkg/cosign/verify_oci_test.go
new file mode 100644
index 00000000..aa80eabf
--- /dev/null
+++ b/pkg/cosign/verify_oci_test.go
@@ -0,0 +1,238 @@
+// Copyright 2025 The Sigstore Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cosign
+
+import (
+	"context"
+	_ "embed"
+	"fmt"
+	"net/http/httptest"
+	"net/url"
+	"testing"
+
+	"github.com/google/go-containerregistry/pkg/name"
+	"github.com/google/go-containerregistry/pkg/registry"
+	"github.com/google/go-containerregistry/pkg/v1/empty"
+	"github.com/google/go-containerregistry/pkg/v1/random"
+	"github.com/google/go-containerregistry/pkg/v1/remote"
+	"github.com/stretchr/testify/assert"
+	"google.golang.org/protobuf/proto"
+
+	ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote"
+	sgbundle "github.com/sigstore/sigstore-go/pkg/bundle"
+	"github.com/sigstore/sigstore-go/pkg/root"
+)
+
+//go:embed testdata/oci-attestation.sigstore.json
+var testAttestation []byte
+
+//go:embed testdata/trusted_root_pgi.json
+var testTrustedRootPGI []byte
+
+func TestGetBundles_Empty(t *testing.T) {
+	r := registry.New(registry.WithReferrersSupport(true))
+	s := httptest.NewServer(r)
+	defer s.Close()
+
+	u, err := url.Parse(s.URL)
+	assert.NoError(t, err)
+
+	ref, err := name.ParseReference(fmt.Sprintf("%s/repo:tag", u.Host))
+	assert.NoError(t, err)
+
+	// If tag doesn't exist, should return ErrImageTagNotFound
+	bundles, hash, err := getBundles(context.Background(), ref, &CheckOpts{})
+	imgTagNotFound := &ErrImageTagNotFound{}
+	assert.ErrorAs(t, err, &imgTagNotFound)
+	assert.Len(t, bundles, 0)
+	assert.Nil(t, hash)
+
+	// Write an image
+	img, err := random.Image(10, 10)
+	assert.NoError(t, err)
+	assert.NoError(t, remote.Write(ref, img))
+
+	// Check that no matching attestation error is returned
+	bundles, hash, err = getBundles(context.Background(), ref, &CheckOpts{})
+	var noMatchErr *ErrNoMatchingAttestations
+	assert.ErrorAs(t, err, &noMatchErr)
+	assert.Len(t, bundles, 0)
+	assert.Nil(t, hash)
+
+	// Get digest from tag
+	desc, err := remote.Head(ref)
+	assert.NoError(t, err)
+	digestRef := ref.Context().Digest(desc.Digest.String())
+
+	// Write invalid attestation
+	err = ociremote.WriteAttestationNewBundleFormat(digestRef, []byte("invalid"), "foo/bar")
+	assert.NoError(t, err)
+
+	// Should still return no matching attestation error, as it failed to parse the bundle
+	bundles, hash, err = getBundles(context.Background(), ref, &CheckOpts{})
+	assert.ErrorAs(t, err, &noMatchErr)
+	assert.Len(t, bundles, 0)
+	assert.Nil(t, hash)
+}
+
+func TestGetBundles_Valid(t *testing.T) {
+	r := registry.New(registry.WithReferrersSupport(true))
+	s := httptest.NewServer(r)
+	defer s.Close()
+
+	u, err := url.Parse(s.URL)
+	assert.NoError(t, err)
+
+	ref, err := name.ParseReference(fmt.Sprintf("%s/repo:tag", u.Host))
+	assert.NoError(t, err)
+
+	// Test data uses empty image
+	assert.NoError(t, remote.Write(ref, empty.Image))
+
+	// Get digest from tag
+	desc, err := remote.Head(ref)
+	assert.NoError(t, err)
+	digestRef := ref.Context().Digest(desc.Digest.String())
+
+	// Write valid test attestation
+	err = ociremote.WriteAttestationNewBundleFormat(digestRef, testAttestation, "https://cosign.sigstore.dev/attestation/v1")
+	assert.NoError(t, err)
+
+	// Retrieve the attestation
+	bundles, hash, err := getBundles(context.Background(), ref, &CheckOpts{})
+	assert.NoError(t, err)
+	assert.Len(t, bundles, 1)
+	assert.NotNil(t, hash)
+
+	// Compare the output to the test data
+	expected := sgbundle.Bundle{}
+	err = expected.UnmarshalJSON(testAttestation)
+	assert.NoError(t, err)
+	if !proto.Equal(bundles[0].Bundle, &expected) {
+		t.Errorf("got %v, want %v", bundles[0].Bundle, &expected)
+	}
+}
+
+// TODO: This test is getting long and maybe should be refactored into a
+// table-based test to exercise more permutations.
+func TestVerifyImageAttestationsSigstoreBundle(t *testing.T) {
+	r := registry.New(registry.WithReferrersSupport(true))
+	s := httptest.NewServer(r)
+	defer s.Close()
+
+	u, err := url.Parse(s.URL)
+	assert.NoError(t, err)
+
+	ref, err := name.ParseReference(fmt.Sprintf("%s/repo:tag", u.Host))
+	assert.NoError(t, err)
+
+	ref2, err := name.ParseReference(fmt.Sprintf("%s/repo:tag2", u.Host))
+	assert.NoError(t, err)
+
+	// Parse test root
+	trustedRoot, err := root.NewTrustedRootFromJSON(testTrustedRootPGI)
+	assert.NoError(t, err)
+
+	// Test data uses empty image
+	assert.NoError(t, remote.Write(ref, empty.Image))
+
+	// Also write a second image to test that we only verify the correct image
+	randomImage, err := random.Image(10, 10)
+	assert.NoError(t, err)
+	assert.NoError(t, remote.Write(ref2, randomImage))
+
+	// Attempt to verify non-existent attestation
+	atts, bundleVerified, err := VerifyImageAttestations(context.Background(), ref, &CheckOpts{
+		TrustedMaterial: trustedRoot,
+		NewBundleFormat: true,
+		Identities: []Identity{
+			{
+				IssuerRegExp:  ".*",
+				SubjectRegExp: ".*",
+			},
+		},
+	})
+	var errNoMatchingAttestations *ErrNoMatchingAttestations
+	assert.ErrorAs(t, err, &errNoMatchingAttestations)
+	assert.False(t, bundleVerified)
+	assert.Len(t, atts, 0)
+
+	// Get digest from tag
+	desc, err := remote.Head(ref)
+	assert.NoError(t, err)
+	digestRef := ref.Context().Digest(desc.Digest.String())
+
+	// Write valid test attestation
+	err = ociremote.WriteAttestationNewBundleFormat(digestRef, testAttestation, "https://cosign.sigstore.dev/attestation/v1")
+	assert.NoError(t, err)
+
+	// Verify the attestation
+	atts, bundleVerified, err = VerifyImageAttestations(context.Background(), ref, &CheckOpts{
+		TrustedMaterial: trustedRoot,
+		NewBundleFormat: true,
+		Identities: []Identity{
+			{
+				IssuerRegExp:  ".*",
+				SubjectRegExp: ".*",
+			},
+		},
+	})
+	assert.NoError(t, err)
+	assert.True(t, bundleVerified)
+	assert.Len(t, atts, 1)
+
+	// Wrong identity should not verify
+	atts, bundleVerified, err = VerifyImageAttestations(context.Background(), ref, &CheckOpts{
+		TrustedMaterial: trustedRoot,
+		NewBundleFormat: true,
+		Identities: []Identity{
+			{
+				IssuerRegExp:  ".*",
+				SubjectRegExp: "wrong",
+			},
+		},
+	})
+	assert.ErrorAs(t, err, &errNoMatchingAttestations)
+	assert.False(t, bundleVerified)
+	assert.Len(t, atts, 0)
+
+	// Add same attestation to different image with different digest to test that we only verify the correct image
+	// Get digest from tag
+	desc, err = remote.Head(ref2)
+	assert.NoError(t, err)
+	digestRef = ref2.Context().Digest(desc.Digest.String())
+
+	// Write valid test attestation
+	err = ociremote.WriteAttestationNewBundleFormat(digestRef, testAttestation, "https://cosign.sigstore.dev/attestation/v1")
+	assert.NoError(t, err)
+
+	// Verify the attestation
+	atts, bundleVerified, err = VerifyImageAttestations(context.Background(), ref2, &CheckOpts{
+		TrustedMaterial: trustedRoot,
+		NewBundleFormat: true,
+		Identities: []Identity{
+			{
+				IssuerRegExp:  ".*",
+				SubjectRegExp: ".*",
+			},
+		},
+	})
+	assert.Error(t, err)
+	assert.ErrorAs(t, err, &errNoMatchingAttestations)
+	// TODO: This error message comes from sigstore-go. We may want to update the upstream with a sentinal error type.
+	assert.ErrorContains(t, err, "provided artifact digest does not match any digest in statement")
+	assert.False(t, bundleVerified)
+	assert.Len(t, atts, 0)
+}
diff --git a/test/e2e_test.go b/test/e2e_test.go
index 48a6d80a..9a9097f7 100644
--- a/test/e2e_test.go
+++ b/test/e2e_test.go
@@ -750,12 +750,15 @@ func TestSignVerifyWithTUFMirror(t *testing.T) {
 }
 
 func TestAttestVerify(t *testing.T) {
-	attestVerify(t,
-		"slsaprovenance",
-		`{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`,
-		`predicate: builder: id: "2"`,
-		`predicate: builder: id: "1"`,
-	)
+	for _, newBundleFormat := range []bool{false, true} {
+		attestVerify(t,
+			newBundleFormat,
+			"slsaprovenance",
+			`{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`,
+			`predicate: builder: id: "2"`,
+			`predicate: builder: id: "1"`,
+		)
+	}
 }
 
 func TestAttestVerifySPDXJSON(t *testing.T) {
@@ -763,12 +766,15 @@ func TestAttestVerifySPDXJSON(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	attestVerify(t,
-		"spdxjson",
-		string(attestationBytes),
-		`predicate: spdxVersion: "SPDX-2.2"`,
-		`predicate: spdxVersion: "SPDX-9.9"`,
-	)
+	for _, newBundleFormat := range []bool{false, true} {
+		attestVerify(t,
+			newBundleFormat,
+			"spdxjson",
+			string(attestationBytes),
+			`predicate: spdxVersion: "SPDX-2.2"`,
+			`predicate: spdxVersion: "SPDX-9.9"`,
+		)
+	}
 }
 
 func TestAttestVerifyCycloneDXJSON(t *testing.T) {
@@ -776,12 +782,15 @@ func TestAttestVerifyCycloneDXJSON(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	attestVerify(t,
-		"cyclonedx",
-		string(attestationBytes),
-		`predicate: specVersion: "1.4"`,
-		`predicate: specVersion: "7.7"`,
-	)
+	for _, newBundleFormat := range []bool{false, true} {
+		attestVerify(t,
+			newBundleFormat,
+			"cyclonedx",
+			string(attestationBytes),
+			`predicate: specVersion: "1.4"`,
+			`predicate: specVersion: "7.7"`,
+		)
+	}
 }
 
 func TestAttestVerifyURI(t *testing.T) {
@@ -789,15 +798,18 @@ func TestAttestVerifyURI(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	attestVerify(t,
-		"https://example.com/TestResult/v1",
-		string(attestationBytes),
-		`predicate: passed: true`,
-		`predicate: passed: false"`,
-	)
+	for _, newBundleFormat := range []bool{false, true} {
+		attestVerify(t,
+			newBundleFormat,
+			"https://example.com/TestResult/v1",
+			string(attestationBytes),
+			`predicate: passed: true`,
+			`predicate: passed: false"`,
+		)
+	}
 }
 
-func attestVerify(t *testing.T, predicateType, attestation, goodCue, badCue string) {
+func attestVerify(t *testing.T, newBundleFormat bool, predicateType, attestation, goodCue, badCue string) {
 	repo, stop := reg(t)
 	defer stop()
 	td := t.TempDir()
@@ -826,6 +838,10 @@ func attestVerify(t *testing.T, predicateType, attestation, goodCue, badCue stri
 		MaxWorkers: 10,
 	}
 
+	if newBundleFormat {
+		verifyAttestation.NewBundleFormat = true
+	}
+
 	// Fail case when using without type and policy flag
 	mustErr(verifyAttestation.Exec(ctx, []string{imgName}), t)
 
@@ -834,7 +850,7 @@ func attestVerify(t *testing.T, predicateType, attestation, goodCue, badCue stri
 	}
 
 	// Now attest the image
-	ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
+	ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc, NewBundleFormat: newBundleFormat}
 	attestCmd := attest.AttestCommand{
 		KeyOpts:        ko,
 		PredicatePath:  attestationPath,
diff --git a/test/e2e_test.sh b/test/e2e_test.sh
index 28eefea0..ce7e2e70 100755
--- a/test/e2e_test.sh
+++ b/test/e2e_test.sh
@@ -73,7 +73,7 @@ for repo in rekor fulcio; do
     count=0
     until [ $(${docker_compose} ps | grep -c "(healthy)") == 3 ];
     do
-        if [ $count -eq 6 ]; then
+        if [ $count -eq 18 ]; then
            echo "! timeout reached"
            exit 1
         else
diff --git a/test/fakeoidc/go.mod b/test/fakeoidc/go.mod
index 81cd9a78..40b447fc 100644
--- a/test/fakeoidc/go.mod
+++ b/test/fakeoidc/go.mod
@@ -2,6 +2,6 @@ module github.com/sigstore/cosign/test/fakeoidc
 
 go 1.23.4
 
-require github.com/go-jose/go-jose/v4 v4.0.4
+require github.com/go-jose/go-jose/v4 v4.0.5
 
-require golang.org/x/crypto v0.31.0 // indirect
+require golang.org/x/crypto v0.32.0 // indirect
diff --git a/test/fakeoidc/go.sum b/test/fakeoidc/go.sum
index a578b86d..3a297911 100644
--- a/test/fakeoidc/go.sum
+++ b/test/fakeoidc/go.sum
@@ -1,14 +1,14 @@
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E=
-github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
+github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
+github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
-github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
-golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
+golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=