Skip to content

Add skills verifier wrapping toolhive-core Sigstore exports - #6091

Open
samuv wants to merge 1 commit into
mainfrom
skills-sig/08-verifier
Open

Add skills verifier wrapping toolhive-core Sigstore exports#6091
samuv wants to merge 1 commit into
mainfrom
skills-sig/08-verifier

Conversation

@samuv

@samuv samuv commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Note

Stack 2 of RFC THV-0080 (tracking issue #5899) — PR 8 of the Sigstore series. Follows #6015 (schema) and #6023 (signer), both merged; parallel with #6084 (git plumbing + storage).

No go.mod change — main already pins toolhive-core v0.0.35, the release carrying stacklok/toolhive-core#192 and stacklok/toolhive-core#197.

Summary

Install-time verification (next PR in the stack) and sync-time offline re-verification need a skills-domain verification API. This adds pkg/skills/verifier as a thin wrapper over toolhive-core's container/verifier exports — all cryptography happens in core; the wrapper adds the skills vocabulary:

  • VerifyOCI(ctx, ref, digest, expected) — keyless (Fulcio) verification. A non-nil expected identity from the lock file is bound into the Sigstore verification policy (core's WithCertificateIdentity), not compared post-hoc — fixing the POC's (Add project-level skills lock file (POC) #5715) biggest gap. Nil expected is the trust-on-first-use case: chain-of-trust verification only, with the caller recording the observed identity.
  • VerifyOCIWithKey(ctx, ref, digest, pubKeyPEM) — the cosign key-pair flow, verifying what thv skill push --key (Add Sigstore signer package for skill OCI artifacts #6023) produces.
  • VerifyBundleOffline(bundle, digest, expected) / ResultFromBundle — re-verify stored bundles against core's embedded trusted root: hermetic, zero network, used by sync and skill adoption later in the stack.
  • Error taxonomy: ErrUnsigned / ErrSignatureInvalid / ErrSignerMismatch as typed sentinels (classification via errors.Is, never string matching). Since the identity is enforced in-policy, a mismatch surfaces as a verification failure; the wrapper tells mismatch apart from a broken signature by re-verifying without the identity constraint — only on the failure path.
  • Result.ToLockProvenance() — maps a verified identity to the lock file's provenance: block; key-signed results (no certificate identity) deliberately yield nil rather than fabricating provenance.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Test improvements
  • CI/CD or build system changes

Test plan

  • Unit tests pass locally (task test)
  • Linting passes (task lint-fix, 0 issues)
  • New unit tests added: full key round trip (the signer package signs an artifact into an in-process registry → core retrieves the key-signed bundle → wrapper verifies with the key); wrong-key rejection; unsigned artifact → ErrUnsigned on both flows; key-signed artifact rejected by the keyless path; offline verification matrix (empty/malformed/certificate-less bundles, and the expected-identity-against-unverifiable-bundle case that must NOT be misclassified as mismatch); ToLockProvenance and identity-conversion tables. Zero network — everything runs against httptest + the embedded trust root.

Does this introduce a user-facing change?

No — nothing consumes the verifier yet. Verification behavior arrives with the install-verification PR.

Special notes for reviewers

  • The ErrSignerMismatch positive path (a bundle that verifies under TOFU but not under the pinned identity) requires a Fulcio-issued certificate and can't be produced hermetically in unit tests; it's covered by the classification logic tests here and will get E2E coverage via the install-verification PR. The inverse case (unverifiable bundle + expected identity stays ErrSignatureInvalid) is tested.
  • Building this PR surfaced the core gap fixed in Reconstruct bundles for key-signed cosign signatures toolhive-core#197: core's bundle reconstruction previously reported key-signed artifacts as unsigned.

Generated with Claude Code

@github-actions github-actions Bot added the size/L Large PR: 600-999 lines changed label Jul 28, 2026
@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 65.38462% with 36 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.41%. Comparing base (ea5ae5c) to head (dafa3d2).

Files with missing lines Patch % Lines
pkg/skills/verifier/oci.go 68.51% 13 Missing and 4 partials ⚠️
pkg/skills/verifier/offline.go 52.17% 8 Missing and 3 partials ⚠️
pkg/skills/verifier/types.go 65.21% 8 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6091      +/-   ##
==========================================
- Coverage   72.43%   72.41%   -0.03%     
==========================================
  Files         733      737       +4     
  Lines       75856    75960     +104     
==========================================
+ Hits        54945    55005      +60     
- Misses      17016    17055      +39     
- Partials     3895     3900       +5     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions github-actions Bot added size/L Large PR: 600-999 lines changed and removed size/L Large PR: 600-999 lines changed labels Jul 28, 2026
@samuv samuv self-assigned this Jul 28, 2026
@samuv
samuv marked this pull request as ready for review July 28, 2026 13:58
@samuv
samuv requested a review from JAORMX as a code owner July 28, 2026 13:58
@github-actions github-actions Bot added size/L Large PR: 600-999 lines changed and removed size/L Large PR: 600-999 lines changed labels Jul 28, 2026
@samuv
samuv force-pushed the skills-sig/08-verifier branch from 56af24b to f41b86e Compare July 28, 2026 15:25
@github-actions github-actions Bot added size/L Large PR: 600-999 lines changed and removed size/L Large PR: 600-999 lines changed labels Jul 28, 2026
Install-time and sync-time signature verification (RFC THV-0080) need
a skills-domain verification API. This adds pkg/skills/verifier as a
thin wrapper over toolhive-core's container/verifier exports: all
cryptography, including binding the expected identity into the
Sigstore verification policy, happens in core. The wrapper adds lock
file provenance conversion, the unsigned/invalid/mismatch error
taxonomy (classified with typed sentinels via errors.Is — a signer
mismatch is told apart from a broken signature by re-verifying without
the identity constraint), and the trust-on-first-use flow where a nil
expected identity verifies the chain of trust only.

Verification uses core's embedded trusted root — hermetic, no TUF
fetch — so unit tests and sync re-verification run without network.
The round-trip test drives the real chain: the signer package signs an
artifact into an in-process registry, core retrieves the key-signed
bundle, and the wrapper verifies it against the key.

Part of #5899.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@samuv
samuv force-pushed the skills-sig/08-verifier branch from f41b86e to dafa3d2 Compare July 28, 2026 16:42
@github-actions github-actions Bot added size/L Large PR: 600-999 lines changed and removed size/L Large PR: 600-999 lines changed labels Jul 28, 2026

@JAORMX JAORMX left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Panel-review comment on the verifier wrapper (reviewed together with #6121 and #6129 against RFC THV-0080). One finding, inline: VerifyBundleOffline/ResultFromBundle have no callers until PR12 — keep with a TODO pointer if PR12 is next, drop if it slips.

The rest of the package is in good shape: binding the expected identity into the Sigstore policy via WithCertificateIdentity (rather than comparing post-hoc) fixes the POC's biggest gap; the failure-path-only re-verification used to distinguish ErrSignerMismatch from ErrSignatureInvalid is safe (no success path, no oracle — both outcomes are 403 to callers); ToLockProvenance returning nil for key-signed results is documented in three places and tested; and the dependency direction (verifierlockfile → core) is acyclic and correct.

Comment thread pkg/skills/verifier/verifier.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/L Large PR: 600-999 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants