signatures not discoverable by cosign - #299
Conversation
Set the OCI artifact type cosign filters on (application/vnd.dev.cosign.artifact.sig.v1+json) in the signature manifest, and also publish the legacy sha256-<digest>.sig tag. Without these, cosign verify reports no signatures found for adapters published by criteria. The change keeps the existing signing payload, annotation keys, subject binding, and empty-JSON config descriptor unchanged so criteria's own verifier continues to accept both pre-existing and newly-published adapters. Tests added/updated: - assert ArtifactType in buildSignatureManifest tests - unit test for the legacy .sig tag shape - unit test proving signArtifact tags the signature manifest when the store supports tagging Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
brokenbot
left a comment
There was a problem hiding this comment.
Approve. Signature manifests now declare artifactType=application/vnd.dev.cosign.artifact.sig.v1+json and publish the legacy sha256-.sig tag, so cosign can discover+verify criteria-published signatures on both OCI-1.1 referrer and legacy-tag paths.
I exercised every in-cycle verification step myself (cosign installed, local registry:2.8, Ed25519 keypair):
- Signed publish ->
cosign verify --keyexits 0 (default legacy-tag path AND COSIGN_EXPERIMENTAL=1 referrers path). - Unsigned publish (distinct manifest digest) ->
cosign verifyexits non-zero ('no signatures found'). - Fallback referrers index entry now carries the correct artifactType (was application/vnd.oci.empty.v1+json).
criteria adapter pullverifies a newly published signed artifact.criteria adapter pullverifies ghcr.io/brokenbots/criteria-adapter-mcp:0.1.0 (pre-change, keyless) -- backward compat holds; verify.go untouched.- make test and make lint clean; all CI gates green.
No 'already correct' guarantees regressed: payload, annotation keys, subject binding, empty-JSON config, criteria verification path, and lock digest pinning all unchanged.
Follow-up doc nits (non-blocking): the cosignSignatureArtifactType comment cites 'wantArtifactType in write_test.go', which does not exist in cosign v3.1.0 -- the real source is ociexperimental.ArtifactType("sig") in internal/pkg/oci/remote/remote.go + pkg/cosign/verify.go. The legacyCosignSignatureTag godoc reads 'sha256--.sig' but the actual format is '-.sig' (e.g. sha256-.sig).
Keyless-on-GHCR remains an operator step (not exercisable in-cycle) per the spec.
Republish criteria-adapter-mcp so its signature carries the cosign artifactType and legacy .sig tag added in #299. The 0.1.0 artifact was published before that fix and is not discoverable by cosign verify. Co-authored-by: ci <ci@localhost>
signatures-not-discoverable-by-cosign
Repository:
brokenbots/criteriaAdapters published by
criteria adapter publishare correctly signed, andcriteria verifies them. No external tool can find those signatures.
cosign verifyreportsno signatures foundagainst every adapter we publish.Confirmed behavior
Measured on 2026-08-02 against
ghcr.io/brokenbots/criteria-adapter-mcp:0.1.0,published by the
Publish MCP Adapterworkflow (run 30781750899, keyless).The signature itself is correct and standards-compliant. Fetching the
signature manifest
sha256:58b4b537290c8c6ecb6cf00e3b45f31522283b1e737e3d16f4ca3d266306d9c7:{ "artifactType": null, "config": "application/vnd.oci.empty.v1+json", "subject": "sha256:5d0d712d3fca3e2e42d880edfac867f5a58e071a9d58c2fc8611679e72e90b8a", "layers": [{ "mediaType": "application/vnd.dev.cosign.simplesigning.v1+json", "annotations": [ "dev.cosignproject.cosign/signature", "dev.sigstore.cosign/bundle", "dev.sigstore.cosign/certificate" ] }] }The payload binds the artifact's manifest digest, the Fulcio certificate and
Rekor bundle are present under the standard annotation keys, and
subjectcorrectly references the signed artifact. Criteria's own verifier reads all of
it — a clean-cache
criteria adapter pullreports:The defect is that
artifactTypeis unset. With noartifactType, theregistry derives the referrer's type from the config media type. The OCI 1.1
referrers fallback index published at tag
sha256-5d0d712d3fca3e2e42d880edfac867f5a58e071a9d58c2fc8611679e72e90b8atherefore advertises the signature as:
{ "artifactType": "application/vnd.oci.empty.v1+json", "digest": "sha256:58b4b537290c8c6ecb6cf00e3b45f31522283b1e737e3d16f4ca3d266306d9c7" }Cosign discovers signatures by filtering referrers on artifact type. Its own
source names the value it looks for:
An
application/vnd.oci.empty.v1+jsonreferrer does not match, so cosignconcludes there is no signature. Verified:
The manifest is built in
internal/adapter/publish/sign.go,buildSignatureManifest(~line 237). It setsMediaType,Config,Subject,and
Layers, and never setsArtifactType.This affects every adapter published by this code path —
criteria-adapter-shell,criteria-adapter-copilot, andcriteria-adapter-noopare all published the same way and are allexternally unverifiable today.
Already correct — do not regress these
payload.Critical.Type = "cosign container image signature", layer media typectypes.SimpleSigningMediaType. Correct; do not change the payload format.dev.cosignproject.cosign/signature,dev.sigstore.cosign/certificate,dev.sigstore.cosign/chain,dev.sigstore.cosign/bundle. These are what cosign reads once it finds themanifest. Do not rename them.
subjectbinding to the signed artifact's descriptor.rejects a push with a zero-value config descriptor (HTTP 500). Whatever
changes, the push must continue to succeed against GHCR.
internal/adapter/signing/verify.gomust keep verifying artifacts published before and after this change. Existing
published adapters must not become unverifiable by criteria.
.criteria.lock.hcldigests must continue to resolve.Required behavior
A signature published by
criteria adapter publishmust be discoverableand verifiable by
cosign verify, for both keyless and explicit-keysigning, without any criteria-specific flag or configuration on the verifying
side.
The signature manifest must declare the artifact type cosign looks for.
Take the value from the cosign dependency rather than hardcoding a string
literal if the module exports it;
github.com/sigstore/cosign/v3is already adirect dependency and
ctypesis already imported by this file. If noexported constant exists, define one in criteria with a comment naming the
cosign source it mirrors.
Signatures must remain discoverable on registries that do not implement the
referrers API. Establish whether the referrers fallback index alone is
sufficient for cosign, or whether the legacy
sha256-<digest>.sigtag mustalso be published. Determine this by testing against a real registry, not by
reading the spec — then implement whichever is required. If both are needed,
publish both.
Previously published adapters remain verifiable by criteria. This change
alters what is written at publish time; it must not alter how criteria reads
artifacts already in registries.
Verification
Exercisable in-cycle — do these:
cosign verify --key <pub> <ref>and show it exits 0.cosign verify, and show it exits non-zero. Use a different manifestdigest for the unsigned case, so a signature attached to the signed digest
cannot satisfy the unsigned verify.
criteria adapter pullstill verifying a newly published artifact.criteria adapter pullstill verifyingghcr.io/brokenbots/criteria-adapter-mcp:0.1.0, which was published beforethis change. This is the backward-compatibility check and it is the one most
likely to be skipped.
make testandmake lintclean.Not exercisable in-cycle — state as unverified:
CI OIDC. Name it as an operator step: republish an adapter after this lands
and run
cosign verifywith the certificate identity pinned.Out of scope
transitionDefaultMode(internal/cli/verification.go), which issigning.ModeWarnunder decision D-WS46-1 pending WS48. Separate concern.