Skip to content

Commit 196d502

Browse files
jdxclaude
andcommitted
fix(lockfile): don't pre-set provenance before verification succeeds
- zig.rs: remove premature minisign provenance from resolve_lock_info; provenance is recorded in install_version_ after download() confirms minisign verification succeeded - github.rs: add defense-in-depth checks that verify the provenance type returned by successful verification matches the lockfile expectation, preventing silent type switches between mechanisms Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0a48ab4 commit 196d502

2 files changed

Lines changed: 26 additions & 6 deletions

File tree

src/backend/github.rs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,10 +1178,10 @@ impl UnifiedGitBackend {
11781178
) -> Result<Option<ProvenanceType>> {
11791179
let settings = Settings::get();
11801180

1181-
// Read the expected provenance from the lockfile. Unlike aqua's .take() approach,
1182-
// we use .clone() here because tv is &ToolVersion. The caller unconditionally
1183-
// overwrites platform_info.provenance with the returned value, so stale data
1184-
// cannot persist after a successful call.
1181+
// Read the expected provenance from the lockfile. We use .clone() because tv is
1182+
// &ToolVersion. The result is validated against this expectation at every return
1183+
// point: successful verification checks type match, and no-verification triggers
1184+
// a downgrade error.
11851185
let platform_key = self.get_platform_key();
11861186
let locked_provenance = tv
11871187
.lock_platforms
@@ -1213,6 +1213,15 @@ impl UnifiedGitBackend {
12131213
.await
12141214
{
12151215
Ok(true) => {
1216+
// Defense-in-depth: verify the result matches the lockfile expectation
1217+
if let Some(ref expected) = locked_provenance
1218+
&& !expected.is_github_attestations()
1219+
{
1220+
return Err(eyre::eyre!(
1221+
"Lockfile requires {expected} provenance for {tv} but github-attestations was verified. \
1222+
This may indicate a provenance type mismatch."
1223+
));
1224+
}
12161225
return Ok(Some(ProvenanceType::GithubAttestations));
12171226
}
12181227
Ok(false) => {
@@ -1238,6 +1247,15 @@ impl UnifiedGitBackend {
12381247
if !skip_slsa && settings.slsa && settings.github.slsa {
12391248
match self.try_verify_slsa(ctx, tv, file_path).await {
12401249
Ok((true, provenance_url)) => {
1250+
// Defense-in-depth: verify the result matches the lockfile expectation
1251+
if let Some(ref expected) = locked_provenance
1252+
&& !expected.is_slsa()
1253+
{
1254+
return Err(eyre::eyre!(
1255+
"Lockfile requires {expected} provenance for {tv} but slsa was verified. \
1256+
This may indicate a provenance type mismatch."
1257+
));
1258+
}
12411259
return Ok(Some(ProvenanceType::Slsa {
12421260
url: provenance_url,
12431261
}));
@@ -1258,7 +1276,7 @@ impl UnifiedGitBackend {
12581276
}
12591277

12601278
// If lockfile recorded provenance but no verification succeeded, it's a downgrade attack
1261-
if let Some(expected) = locked_provenance {
1279+
if let Some(ref expected) = locked_provenance {
12621280
return Err(eyre::eyre!(
12631281
"Lockfile requires {expected} provenance for {tv} but verification was not performed. \
12641282
This may indicate a downgrade attack. Enable the corresponding verification setting \

src/plugins/core/zig.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,9 @@ impl Backend for ZigPlugin {
422422
};
423423

424424
// Try to get full info from JSON (includes checksum and size)
425+
// Don't pre-set provenance at lock time — minisign verification hasn't run yet.
426+
// Provenance is recorded in install_version_ after download() confirms minisign
427+
// verification succeeded.
425428
match self
426429
.get_download_info_from_json(json_url, version, arch, os)
427430
.await
@@ -430,7 +433,6 @@ impl Backend for ZigPlugin {
430433
url: Some(url),
431434
checksum,
432435
size,
433-
provenance: Some(ProvenanceType::Minisign),
434436
..Default::default()
435437
}),
436438
Err(_) if regex!(r"^\d+\.\d+\.\d+$").is_match(&tv.version) => {

0 commit comments

Comments
 (0)