Skip to content

Commit 7917c6c

Browse files
committed
Fix ambiguity check to include vendor base_purl
The ambiguity check in and only looked at vendor ledger keys, but selection also matches via VendorEntry::matches_target. For golang packages, keys are case-encoded (e.g., ) while is decoded (e.g., ), so a name like could appear unique during ambiguity checking but then match multiple packages during selection. The fix includes both the ledger key AND the in the ambiguity check candidates, ensuring that ambiguous names are properly detected and refused before any mutation occurs.
1 parent 715ff5b commit 7917c6c

58 files changed

Lines changed: 1179 additions & 383 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎crates/socket-patch-cli/src/commands/list.rs‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,10 @@ pub async fn run(args: ListArgs) -> i32 {
431431
detail: detail.clone(),
432432
});
433433
} else if !args.common.silent {
434-
eprintln!("Warning: {}", crate::commands::rollback::capitalize_first(detail));
434+
eprintln!(
435+
"Warning: {}",
436+
crate::commands::rollback::capitalize_first(detail)
437+
);
435438
}
436439
}
437440
let vendor_state = crate::commands::vendor_state_lenient(&loaded.vendor, args.common.silent);
@@ -773,12 +776,18 @@ mod tests {
773776
let listings = HostedListing::from_pins(
774777
&[
775778
pin("pkg:npm/minimist@1.2.2", &record.uuid),
776-
pin("pkg:npm/other@1.0.0", "33333333-3333-4333-8333-333333333333"),
779+
pin(
780+
"pkg:npm/other@1.0.0",
781+
"33333333-3333-4333-8333-333333333333",
782+
),
777783
],
778784
Some(&legacy),
779785
);
780786
assert_eq!(listings[0].record, record);
781-
assert_eq!(listings[1].record.uuid, "33333333-3333-4333-8333-333333333333");
787+
assert_eq!(
788+
listings[1].record.uuid,
789+
"33333333-3333-4333-8333-333333333333"
790+
);
782791
assert!(listings[1].record.vulnerabilities.is_empty());
783792
assert_eq!(listings[1].lockfiles, vec!["yarn.lock".to_string()]);
784793
}

‎crates/socket-patch-cli/src/commands/mod.rs‎

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
pub mod apply;
22
pub(crate) mod bun_preflight;
3-
pub(crate) mod context;
43
pub(crate) mod composer_hints;
4+
pub(crate) mod context;
55
pub(crate) mod fetch_stage;
66
pub mod get;
77
pub mod hosted_bundle;
88
pub mod list;
99
pub(crate) mod lock_cli;
1010
pub mod remove;
1111
pub mod repair;
12-
pub(crate) mod vendored_backend;
1312
pub mod rollback;
1413
pub mod scan;
1514
pub mod update;
1615
pub mod vendor;
16+
pub(crate) mod vendored_backend;
1717
pub mod vex;
1818
pub(crate) mod vex_consumed;
1919
pub(crate) mod vex_sources;
@@ -141,9 +141,11 @@ pub(crate) async fn hosted_state_from_lockfiles(
141141
common: &crate::args::GlobalArgs,
142142
root: &Path,
143143
) -> socket_patch_core::patch::redirect::RedirectState {
144-
hosted_state_from_pins(&socket_patch_core::patch::redirect::upstream::HostedPin::all(
145-
&discover_wiring(common, root).await,
146-
))
144+
hosted_state_from_pins(
145+
&socket_patch_core::patch::redirect::upstream::HostedPin::all(
146+
&discover_wiring(common, root).await,
147+
),
148+
)
147149
}
148150

149151
/// [`hosted_state_from_lockfiles`] over already-discovered pins. A purl
@@ -153,18 +155,17 @@ pub(crate) fn hosted_state_from_pins(
153155
) -> socket_patch_core::patch::redirect::RedirectState {
154156
let mut state = socket_patch_core::patch::redirect::RedirectState::new();
155157
for pin in pins {
156-
state
157-
.records
158-
.entry(pin.purl.clone())
159-
.or_insert_with(|| socket_patch_core::manifest::schema::PatchRecord {
158+
state.records.entry(pin.purl.clone()).or_insert_with(|| {
159+
socket_patch_core::manifest::schema::PatchRecord {
160160
uuid: pin.uuid.clone(),
161161
exported_at: String::new(),
162162
files: Default::default(),
163163
vulnerabilities: Default::default(),
164164
description: String::new(),
165165
license: String::new(),
166166
tier: String::new(),
167-
});
167+
}
168+
});
168169
}
169170
state
170171
}
@@ -191,4 +192,3 @@ pub(crate) fn vendor_state_lenient(
191192
}
192193
}
193194
}
194-

‎crates/socket-patch-cli/src/commands/remove.rs‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -464,15 +464,21 @@ pub async fn run(args: RemoveArgs) -> i32 {
464464
// `@angular/core` and `@babel/core`) is refused across every store:
465465
// `remove` acts on one package per name.
466466
{
467-
let ledger_purls: Vec<&str> = vendor_state_result
467+
let ledger_candidates: Vec<&str> = vendor_state_result
468468
.as_ref()
469-
.map(|state| state.entries.keys().map(String::as_str).collect())
469+
.map(|state| {
470+
state
471+
.entries
472+
.iter()
473+
.flat_map(|(k, e)| [k.as_str(), e.base_purl.as_str()])
474+
.collect()
475+
})
470476
.unwrap_or_default();
471477
let candidates = manifest
472478
.patches
473479
.keys()
474480
.map(String::as_str)
475-
.chain(ledger_purls)
481+
.chain(ledger_candidates)
476482
.chain(hosted_pins.iter().map(|pin| pin.purl.as_str()));
477483
if let Some(msg) = target.ambiguity(candidates) {
478484
emit_error_envelope(

‎crates/socket-patch-cli/src/commands/rollback.rs‎

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1252,13 +1252,15 @@ pub async fn run(args: RollbackArgs) -> i32 {
12521252
let mut identifiers = identifiers;
12531253
let mut globs: Vec<String> = Vec::new();
12541254
for raw in path_scope.raw() {
1255-
let named = is_name_shaped_path(raw).then(|| Target::parse(raw)).filter(|t| {
1256-
t.kind() == TargetKind::Name
1257-
&& (!ledgers.matching(t).is_empty()
1258-
|| redirect_records
1259-
.iter()
1260-
.any(|(purl, uuid)| t.matches_patch(purl, uuid)))
1261-
});
1255+
let named = is_name_shaped_path(raw)
1256+
.then(|| Target::parse(raw))
1257+
.filter(|t| {
1258+
t.kind() == TargetKind::Name
1259+
&& (!ledgers.matching(t).is_empty()
1260+
|| redirect_records
1261+
.iter()
1262+
.any(|(purl, uuid)| t.matches_patch(purl, uuid)))
1263+
});
12621264
match named {
12631265
Some(t) => identifiers.push(t),
12641266
None => globs.push(raw.clone()),
@@ -1301,7 +1303,12 @@ pub async fn run(args: RollbackArgs) -> i32 {
13011303
.manifest
13021304
.iter()
13031305
.map(String::as_str)
1304-
.chain(found.vendor.iter().map(|(k, _)| k.as_str()))
1306+
.chain(
1307+
found
1308+
.vendor
1309+
.iter()
1310+
.flat_map(|(k, e)| [k.as_str(), e.base_purl.as_str()]),
1311+
)
13051312
.chain(hosted_found),
13061313
);
13071314
manifest_scope.extend(found.manifest);

‎crates/socket-patch-cli/src/commands/scan/discovery.rs‎

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -168,29 +168,32 @@ pub(crate) async fn vendored_ledger_supplement(
168168
}
169169
// `(ledger key, base purl, entry)`; the artifact fallback has no
170170
// entries to probe, so it never reports unwired keys.
171-
let candidates: Vec<(String, String, Option<&socket_patch_core::vendor::VendorEntry>)> =
172-
match state {
173-
Ok(state) => state
174-
.entries
175-
.iter()
176-
.map(|(key, entry)| {
177-
(
178-
key.clone(),
179-
strip_purl_qualifiers(&entry.base_purl).to_string(),
180-
Some(entry),
181-
)
182-
})
183-
.collect(),
184-
// Corrupt/unreadable ledger (a MISSING file is Ok(empty) above):
185-
// recover the vendored set from the committed artifacts, or
186-
// `scan --prune` (whose ledger exemption also degrades to empty)
187-
// would delete still-vendored packages' manifest entries and blobs.
188-
Err(_) => vendored_purls_from_artifacts(common)
189-
.await
190-
.into_iter()
191-
.map(|base| (base.clone(), base, None))
192-
.collect(),
193-
};
171+
let candidates: Vec<(
172+
String,
173+
String,
174+
Option<&socket_patch_core::vendor::VendorEntry>,
175+
)> = match state {
176+
Ok(state) => state
177+
.entries
178+
.iter()
179+
.map(|(key, entry)| {
180+
(
181+
key.clone(),
182+
strip_purl_qualifiers(&entry.base_purl).to_string(),
183+
Some(entry),
184+
)
185+
})
186+
.collect(),
187+
// Corrupt/unreadable ledger (a MISSING file is Ok(empty) above):
188+
// recover the vendored set from the committed artifacts, or
189+
// `scan --prune` (whose ledger exemption also degrades to empty)
190+
// would delete still-vendored packages' manifest entries and blobs.
191+
Err(_) => vendored_purls_from_artifacts(common)
192+
.await
193+
.into_iter()
194+
.map(|base| (base.clone(), base, None))
195+
.collect(),
196+
};
194197
// Composer by release identity: a ledger `@3.0.2.0` is the crawled
195198
// `@3.0.2`, not a second package to supplement.
196199
let key = |p: &str| composer_purl_identity(p).unwrap_or_else(|| normalize_purl(p).into_owned());
@@ -1045,7 +1048,9 @@ mod tests {
10451048
..GlobalArgs::default()
10461049
};
10471050
let state = socket_patch_core::vendor::load_state(root).await;
1048-
vendored_ledger_supplement(&args, crawled, &state).await.packages
1051+
vendored_ledger_supplement(&args, crawled, &state)
1052+
.await
1053+
.packages
10491054
}
10501055

10511056
/// A ledger entry vendored as `@3.0.2.0` is the crawled composer
@@ -1080,7 +1085,9 @@ mod tests {
10801085
out.iter().map(|p| &p.purl).collect::<Vec<_>>()
10811086
);
10821087

1083-
let out = vendored_ledger_supplement(&args, &[], &Ok(state)).await.packages;
1088+
let out = vendored_ledger_supplement(&args, &[], &Ok(state))
1089+
.await
1090+
.packages;
10841091
assert_eq!(
10851092
out.iter().map(|p| p.purl.as_str()).collect::<Vec<_>>(),
10861093
vec!["pkg:composer/psr/log@3.0.2.0"]
@@ -1183,7 +1190,10 @@ mod tests {
11831190
let state = npm_ledger_with_lock(tmp.path(), lock.as_deref()).await;
11841191
let out = vendored_ledger_supplement(&args, &[], &state).await;
11851192
assert_eq!(
1186-
out.packages.iter().map(|p| p.purl.as_str()).collect::<Vec<_>>(),
1193+
out.packages
1194+
.iter()
1195+
.map(|p| p.purl.as_str())
1196+
.collect::<Vec<_>>(),
11871197
vec!["pkg:npm/left-pad@1.3.0"],
11881198
"lock={lock:?}"
11891199
);

‎crates/socket-patch-cli/src/commands/scan/hosted.rs‎

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -988,7 +988,8 @@ pub(crate) async fn run_redirect_selected(
988988
socket_patch_core::utils::fs::read_regular_to_string_sync(path).ok()
989989
})
990990
};
991-
let rewrite_options = || RewriteOptions {
991+
let rewrite_options = || {
992+
RewriteOptions {
992993
dry_run: common.dry_run,
993994
targets_pipenv_lock,
994995
pipenv_major,
@@ -1000,6 +1001,7 @@ pub(crate) async fn run_redirect_selected(
10001001
npm_allow_remote_config: !common.no_npm_allow_remote_config,
10011002
npm_outer: &npm_outer,
10021003
blocking: true,
1004+
}
10031005
};
10041006
// The rollout gate plans again without its deferred rows: keep what
10051007
// the second pass needs.
@@ -4744,19 +4746,43 @@ mod tests {
47444746
use super::npm_allow_remote_one_line;
47454747
let hosts = ["patch.socket.dev"];
47464748
let cases = [
4747-
(npm_allow_remote_configured_detail(&hosts, true, false), "Note: set"),
4748-
(npm_allow_remote_configured_detail(&hosts, false, false), "Note: set"),
4749-
(npm_allow_remote_configured_detail(&hosts, true, true), "Note: would set"),
4750-
(npm_allow_remote_already_detail(&hosts), "Note: .npmrc already"),
4751-
(npm_allow_remote_user_set_detail(&hosts, "none"), "Warning: npm >=12"),
4752-
(npm_allow_remote_env_set_detail(&hosts, "npm_config_allow_remote", "none"), "Warning: npm >=12"),
4749+
(
4750+
npm_allow_remote_configured_detail(&hosts, true, false),
4751+
"Note: set",
4752+
),
4753+
(
4754+
npm_allow_remote_configured_detail(&hosts, false, false),
4755+
"Note: set",
4756+
),
4757+
(
4758+
npm_allow_remote_configured_detail(&hosts, true, true),
4759+
"Note: would set",
4760+
),
4761+
(
4762+
npm_allow_remote_already_detail(&hosts),
4763+
"Note: .npmrc already",
4764+
),
4765+
(
4766+
npm_allow_remote_user_set_detail(&hosts, "none"),
4767+
"Warning: npm >=12",
4768+
),
4769+
(
4770+
npm_allow_remote_env_set_detail(&hosts, "npm_config_allow_remote", "none"),
4771+
"Warning: npm >=12",
4772+
),
47534773
(npm_allow_remote_manual_detail(&hosts), "Warning: npm >=12"),
4754-
(npm_allow_remote_unreadable_detail(&hosts, "is a symlink"), "Warning: npm >=12"),
4774+
(
4775+
npm_allow_remote_unreadable_detail(&hosts, "is a symlink"),
4776+
"Warning: npm >=12",
4777+
),
47554778
];
47564779
for (detail, start) in cases {
47574780
let line = npm_allow_remote_one_line(&detail);
47584781
assert!(line.starts_with(start), "{line}");
4759-
assert!(!line.contains('\n') && line.ends_with("(details: --verbose)."), "{line}");
4782+
assert!(
4783+
!line.contains('\n') && line.ends_with("(details: --verbose)."),
4784+
"{line}"
4785+
);
47604786
}
47614787
}
47624788
}

0 commit comments

Comments
 (0)