Record certificate ref and runner in lock provenance - #6312
Merged
Conversation
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>
samuv
requested review from
JAORMX,
amirejaz,
aponcedeleonch,
rdimitrov and
reyortiz3
as code owners
August 13, 2026 15:10
Codecov Report✅ All modified and coverable lines are covered by tests. 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. 🚀 New features to boost your workflow:
|
rdimitrov
approved these changes
Aug 13, 2026
4 tasks
This was referenced Aug 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
1.3.6.1.4.1.57264.1.14) and the runner environment it executed in (OID1.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.repositoryRef/runnerEnvironmentto 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.Part of #6309.
Type of change
Test plan
task test)task test-e2e)task lint-fix)Tests added:
TestProvenanceWithoutRefFieldsIsUnconstrained(pkg/skills/lockfile) — a hand-written lock file with aprovenance:block predating both fields loads with them empty, does not leak them into the inlineExtramap, 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.validateLockfiletable cases — both fields accepted when well-formed, and rejected for control characters / over-length, matching the existingrepositoryUritreatment.TestProvenanceConversionsPreserveEveryField(pkg/skills/skillsvc) — reflection-backed guard overprovenanceInfoFromLock/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/clientwire round-trips — a case inTestInstallCarriesTrustStateBackToCallerand one inTestInfocarrying 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 testpasses apart fromTestMCPGoClientInitializeAndPinginpkg/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 pluspkg/api/v1andcmd/thv/appall 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
provenanceInfoFromResultis deliberately untouched:verifier.Resulthas 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.skills.ProvenanceInfodirectly rather than defining a parallel response DTO, so no separate API type or conversion function needed changing —pkg/skills/client/dto.goandpkg/api/v1/skills_types.goboth reference the same struct.Generated with Claude Code