Skip to content

Record certificate ref and runner in lock provenance - #6312

Merged
samuv merged 1 commit into
mainfrom
skills-refpin/01-schema-and-dto
Aug 13, 2026
Merged

Record certificate ref and runner in lock provenance#6312
samuv merged 1 commit into
mainfrom
skills-refpin/01-schema-and-dto

Conversation

@samuv

@samuv samuv commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • A Fulcio certificate carries more identity than the lock file records. Two extensions are currently discarded: the git ref the signing workflow ran on (OID 1.3.6.1.4.1.57264.1.14) and the runner environment it executed in (OID 1.3.6.1.4.1.57264.1.11). Without them, a recorded trust anchor means "this repository and workflow, on any ref, from any runner" — a workflow triggered on an attacker-pushed branch still satisfies the lock entry.
  • Recording them tightens the guarantee to "this repository and workflow, this ref, this runner class", which is the trust model the follow-up enforcement change needs to compare against.
  • This PR is schema and plumbing only: it adds repositoryRef / runnerEnvironment to the lock provenance block and threads them through the existing lock ↔ service ↔ API conversions so nothing is dropped in transit. Extraction from the certificate and enforcement against the locked values land in a follow-up PR, so the fields stay empty for freshly verified installs until then.
  • Both fields are optional and absent means unconstrained. Every lock file written before this change omits them, so old locks must load, re-save without gaining keys, and behave exactly as before — pinned by a dedicated test.

Part of #6309.

Type of change

  • Bug fix
  • New feature
  • Refactoring (no behavior change)
  • Dependency update
  • Documentation
  • Other (describe):

Test plan

  • Unit tests (task test)
  • E2E tests (task test-e2e)
  • Linting (task lint-fix)
  • Manual testing (describe below)

Tests added:

  • TestProvenanceWithoutRefFieldsIsUnconstrained (pkg/skills/lockfile) — a hand-written lock file with a provenance: block predating both fields loads with them empty, does not leak them into the inline Extra map, re-saves without the new keys appearing, and reaches a byte-stable form so an install by a newer binary produces no spurious diff for entries it did not touch.
  • TestProvenanceRoundTrip (pkg/skills/lockfile) — extended so both new fields round-trip through Save → Load when present.
  • validateLockfile table cases — both fields accepted when well-formed, and rejected for control characters / over-length, matching the existing repositoryUri treatment.
  • TestProvenanceConversionsPreserveEveryField (pkg/skills/skillsvc) — reflection-backed guard over provenanceInfoFromLock / provenanceInfoToLock: it fails if any field of either provenance shape is left zero after conversion, so a future field added to one shape but forgotten in a conversion is caught here. Verified it actually fails by temporarily deleting a field from a conversion.
  • pkg/skills/client wire round-trips — a case in TestInstallCarriesTrustStateBackToCaller and one in TestInfo carrying both new fields across the HTTP boundary. The CLI is a pure HTTP client, so a field the service sets but the wire shape drops is invisible in production even though a printer-level unit test still passes; this has bitten this code path before.

task test passes apart from TestMCPGoClientInitializeAndPing in pkg/transport/proxy/streamable, which binds hardcoded port 8096 and fails on this machine because another process holds it. Unrelated to this change (no shared code); the touched packages plus pkg/api/v1 and cmd/thv/app all pass under the Taskfile's flags.

Does this introduce a user-facing change?

No. The lock file gains two optional keys that nothing populates yet, and no CLI output changed. docs/server/* is regenerated (task docs) because the new fields are part of the API response schema.

Special notes for reviewers

  • provenanceInfoFromResult is deliberately untouched: verifier.Result has no ref or runner fields yet, so adding them there is the follow-up PR's job. The consequence is that the new fields stay empty whenever provenance is populated from a fresh verification, and only carry values once extraction lands.
  • The API layer reuses skills.ProvenanceInfo directly rather than defining a parallel response DTO, so no separate API type or conversion function needed changing — pkg/skills/client/dto.go and pkg/api/v1/skills_types.go both reference the same struct.
  • Validation intentionally does not fail on absence. The whole point of the compatibility contract is that an empty value means unconstrained; only a present-but-malformed value is rejected.

Generated with Claude Code

A Fulcio certificate carries more identity than the lock file records: the
git ref the signing workflow ran on (OID 1.3.6.1.4.1.57264.1.14) and the
runner environment it executed in (OID 1.3.6.1.4.1.57264.1.11). Without
them the recorded trust anchor means "this repo and workflow, on any ref,
from any runner", so a workflow triggered on an attacker-pushed branch
still satisfies the lock.

Record both in the provenance block and thread them through the lock,
service, and API shapes. Both are optional: every lock file written before
this change omits them, and absent must keep meaning unconstrained.

Extraction from the certificate and enforcement against the locked values
land in a follow-up change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions github-actions Bot added the size/S Small PR: 100-299 lines changed label Aug 13, 2026
@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 72.83%. Comparing base (0302206) to head (1c668f0).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6312      +/-   ##
==========================================
- Coverage   72.85%   72.83%   -0.02%     
==========================================
  Files         742      742              
  Lines       77804    77810       +6     
==========================================
- Hits        56683    56675       -8     
- Misses      17145    17159      +14     
  Partials     3976     3976              

☔ 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.

@samuv samuv self-assigned this Aug 13, 2026
@samuv
samuv merged commit 17f0708 into main Aug 13, 2026
81 of 86 checks passed
@samuv
samuv deleted the skills-refpin/01-schema-and-dto branch August 13, 2026 16:08
@github-actions github-actions Bot mentioned this pull request Aug 14, 2026
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/S Small PR: 100-299 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants