You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
thv skill push can only sign with a file-based cosign key. Keyless (OIDC → Fulcio) signing returns ErrKeyRequired, and has since the signer landed. This is the last real gap in the publish story: every artifact ToolHive consumes from the catalog is keyless-signed by GitHub Actions OIDC, and the verify path handles that fine — we just can't produce one.
Deferred from v1 in #5740 and tracked as a bullet in #5899 (now closed); this gives it a home.
Why it is not just "call the keyless API"
Signing happens server-side.thv skill push is a pure HTTP client; the thv serve process does the signing. That shapes the whole design:
A CI publisher has an ambient OIDC token, but it belongs to the job, not to the long-lived server process the CLI talks to.
A human at a laptop gets a token through an interactive browser flow, which a daemon cannot reasonably initiate on their behalf.
The same split already surprises people with key signing: COSIGN_PASSWORD must be set on the server's environment, not the user's shell. Keyless makes that sharper, because a token is short-lived and request-scoped rather than a static value an operator can export once.
So the design question is not "which sigstore API" but how a request-scoped credential reaches the signer. Options worth weighing, none obviously right:
Forward an OIDC token from the CLI in the push request. Simple, but puts a bearer credential in an API body.
Have the server acquire its own token from ambient credentials. Works in CI, not for a laptop.
Sign client-side and have the server only attach. Splits the signer, but keeps the credential where it belongs.
Scope
Keyless signing path in container/signer (toolhive-core) — see the note below on where the code should live.
Whatever credential plumbing the chosen option needs, in thv skill push and the push API.
E2E against Sigstore staging, which was always paired with this in the deferral. Unit tests must stay network-free; the existing suite uses embedded TUF roots and test keys.
Notes before starting
pkg/skills/signer (toolhive) and container/signer (toolhive-core) are duplicates right now. Core is the intended home and Add container/signer for cosign key-based OCI signing toolhive-core#230 merged it there; deleting the local copy is blocked only on a core release. New signing work should almost certainly go to core — confirm before writing code, it changes the shape of the work.
sigstore-go/pkg/sign is already a direct dependency of core and supports keyless.
Signature attachment must append to the existing .sig manifest, never replace it, and dedupe by verifying against the public key — ECDSA is randomised, so byte comparison never matches. See toolhive-core#230.
SignOCI returns *Result{Bundle, PayloadDigest}; the payload digest is not the artifact digest.
Interop is verified by signing with thv and verifying with the stock cosign CLI against a local registry:2. Our verifier accepting our own bundles proves less than it looks — that gap previously hid a bug where --key could not read any cosign generate-key-pair output.
Acceptance criteria
thv skill push can sign keylessly, and the resulting artifact verifies with cosign verify --certificate-identity ... --certificate-oidc-issuer ...
The credential path is documented, including what happens when no OIDC token is available (must fail with a clear message, never silently push unsigned)
Existing key-based signing is unchanged
E2E against Sigstore staging; unit tests remain network-free
Summary
thv skill pushcan only sign with a file-based cosign key. Keyless (OIDC → Fulcio) signing returnsErrKeyRequired, and has since the signer landed. This is the last real gap in the publish story: every artifact ToolHive consumes from the catalog is keyless-signed by GitHub Actions OIDC, and the verify path handles that fine — we just can't produce one.Deferred from v1 in #5740 and tracked as a bullet in #5899 (now closed); this gives it a home.
Why it is not just "call the keyless API"
Signing happens server-side.
thv skill pushis a pure HTTP client; thethv serveprocess does the signing. That shapes the whole design:COSIGN_PASSWORDmust be set on the server's environment, not the user's shell. Keyless makes that sharper, because a token is short-lived and request-scoped rather than a static value an operator can export once.So the design question is not "which sigstore API" but how a request-scoped credential reaches the signer. Options worth weighing, none obviously right:
Scope
container/signer(toolhive-core) — see the note below on where the code should live.thv skill pushand the push API.Notes before starting
pkg/skills/signer(toolhive) andcontainer/signer(toolhive-core) are duplicates right now. Core is the intended home and Add container/signer for cosign key-based OCI signing toolhive-core#230 merged it there; deleting the local copy is blocked only on a core release. New signing work should almost certainly go to core — confirm before writing code, it changes the shape of the work.sigstore-go/pkg/signis already a direct dependency of core and supports keyless..sigmanifest, never replace it, and dedupe by verifying against the public key — ECDSA is randomised, so byte comparison never matches. See toolhive-core#230.SignOCIreturns*Result{Bundle, PayloadDigest}; the payload digest is not the artifact digest.thvand verifying with the stock cosign CLI against a localregistry:2. Our verifier accepting our own bundles proves less than it looks — that gap previously hid a bug where--keycould not read anycosign generate-key-pairoutput.Acceptance criteria
thv skill pushcan sign keylessly, and the resulting artifact verifies withcosign verify --certificate-identity ... --certificate-oidc-issuer ...