diff --git a/scripts/run-push-containers b/scripts/run-push-containers
index 3ed43ae..fd606c0 100755
--- a/scripts/run-push-containers
+++ b/scripts/run-push-containers
@@ -19,7 +19,7 @@
 #                              successfully pushed image is signed with
 #                              cosign (otherwise signing is skipped). Also
 #                              gates SPDX SBOM attestation: each pushed image
-#                              gets a 'cosign attest --type spdxjson'
+#                              gets an in-toto SPDX 3.x ('cosign attest')
 #                              attestation of its SPDX SBOM (per-arch on the
 #                              multi-arch path, top manifest on single-arch).
 #   CONTAINER_COSIGN_PUB     - cosign public key path; when set, each
@@ -69,6 +69,10 @@ cosign_key = utils.getconfigvar("CONTAINER_COSIGN_KEY", ourconfig, args.target,
 # Public key for verifying attestations; when unset, attestations are made but
 # not verified.
 cosign_pub = utils.getconfigvar("CONTAINER_COSIGN_PUB", ourconfig, args.target, args.stepnum)
+# in-toto predicate type URI for SPDX 3.x documents. Must be identical on
+# 'attest' and 'verify-attestation', or verification finds no matching
+# attestation.
+SPDX_PREDICATE_TYPE = "https://spdx.dev/Document/v3"
 
 utils.printheader("Pushing container images %s" % list(container_images.keys()))
 
@@ -269,7 +273,13 @@ sys.stdout.write(base64.b64decode(t).decode() if t else "")' "%s" "$_host")
         "    prepare_cosign",
         "    case \" $_ATTESTED_REFS \" in *\" $1 \"*) return 0 ;; esac",
         "    _ATTESTED_REFS=\"$_ATTESTED_REFS $1\"",
-        "    _att_out=$(oe-run-native cosign-native cosign attest --key %s --type spdxjson --predicate \"$2\" \"$1\" 2>&1) || {" % cosign_key,
+        # --type is the in-toto predicate type URI, not cosign's 'spdxjson'
+        # alias: that alias hardcodes https://spdx.dev/Document, which is the
+        # SPDX 2.x predicate. create_image_sbom_spdx emits SPDX 3.0.1, so we
+        # pass the v3 URI explicitly. For an unknown (non-alias) type cosign
+        # parses the predicate as JSON and embeds it verbatim, which is what
+        # we want for the SPDX 3 JSON-LD document.
+        "    _att_out=$(oe-run-native cosign-native cosign attest --key %s --type %s --predicate \"$2\" \"$1\" 2>&1) || {" % (cosign_key, SPDX_PREDICATE_TYPE),
         "        case \"$_att_out\" in",
         "            *\"already exists\"*|*createLogEntryConflict*) echo \"cosign: attestation for $1 already in transparency log\" ;;",
         "            *) echo \"$_att_out\" >&2; return 1 ;;",
@@ -279,7 +289,7 @@ sys.stdout.write(base64.b64decode(t).decode() if t else "")' "%s" "$_host")
     ]
     if cosign_pub:
         script += [
-            "    oe-run-native cosign-native cosign verify-attestation --key %s --type spdxjson \"$1\" >/dev/null \\" % cosign_pub,
+            "    oe-run-native cosign-native cosign verify-attestation --key %s --type %s \"$1\" >/dev/null \\" % (cosign_pub, SPDX_PREDICATE_TYPE),
             "        && echo \"cosign: verified attestation for $1\" \\",
             "        || { echo \"ERROR: attestation verification failed for $1\" >&2; return 1; }",
         ]
