Skip to content

Record the certificate's source ref and runner environment in lock provenance #6309

Description

@samuv

Summary

The provenance we pin identifies a repository and a workflow file, but not the git ref. A signature produced by that same workflow on any branch of that repository satisfies the policy.

Surfaced while explaining the trust model; never in scope for v1.

What is recorded today

provenance:
  signerIdentity: /.github/workflows/build-skills.yml
  certIssuer: https://token.actions.githubusercontent.com
  repositoryUri: https://github.com/stacklok/dockyard
  sigstoreUrl: https://rekor.sigstore.dev

signerIdentity is the SAN with the repository URI trimmed off the front and the ref cut from the end. toolhive-core's signerIdentityFromCertificate does:

builderURL, _, _ = strings.Cut(builderURL, "@")            // drops @refs/heads/main
builderURL = strings.TrimPrefix(builderURL, c.SourceRepositoryURI)

and the verification policy reassembles them with a trailing (@.*)?$, which is what makes any ref match.

What the certificate actually carries

A real Fulcio cert from ghcr.io/stacklok/dockyard/skills/tdd:0.1.0:

OID Value
.1.5 repository stacklok/dockyard
.1.6 ref refs/heads/main
.1.3 commit 29c7bc58fa8d1042ff73a9154e120c022cf908df
.1.11 runner github-hosted
.1.21 run URL .../actions/runs/25171716698/attempts/1
.1.22 visibility public

So the information exists and is simply discarded.

Why it matters

The guarantee reduces to "someone who can make that workflow path run in that repository". Branch protection on main does not help if a signature from any branch is accepted. Recording .1.6 and .1.11 would tighten it to "on this ref, on a hosted runner".

toolhive-core already knows how to compare both — compareBaseProperties checks RepositoryRef and RunnerEnvironment for MCP server provenance. The skills lock just never records them.

Scope and the compatibility question

  • Add the fields to lockfile.Provenance and populate them from the verification result.
  • Bind them into the verification policy where core supports it.
  • Existing lock files have neither field. Enforcing them against entries written before this change would break every project on upgrade. Decide deliberately: absent means unconstrained (backward compatible, weaker), or absent triggers a re-pin. This is the crux of the issue, not an implementation detail.

Related: #6307 (keyless signing produces these certs).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requestgoPull requests that update go codeskillsSkills lifecycle management

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions