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).
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
signerIdentityis the SAN with the repository URI trimmed off the front and the ref cut from the end. toolhive-core'ssignerIdentityFromCertificatedoes: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:.1.5repositorystacklok/dockyard.1.6refrefs/heads/main.1.3commit29c7bc58fa8d1042ff73a9154e120c022cf908df.1.11runnergithub-hosted.1.21run URL.../actions/runs/25171716698/attempts/1.1.22visibilitypublicSo 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
maindoes not help if a signature from any branch is accepted. Recording.1.6and.1.11would tighten it to "on this ref, on a hosted runner".toolhive-core already knows how to compare both —
compareBasePropertieschecksRepositoryRefandRunnerEnvironmentfor MCP server provenance. The skills lock just never records them.Scope and the compatibility question
lockfile.Provenanceand populate them from the verification result.Related: #6307 (keyless signing produces these certs).