Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 71 additions & 1 deletion crates/socket-patch-core/src/patch/redirect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3519,6 +3519,7 @@ fn rewrite_yarn_classic(
Regex::new(&(String::from(r#"\n {2}version ""#) + &regex::escape(&dep.version) + "\""))
.expect("version regex from the escaped version is valid");
let mut matched_any = false;
let mut pinned_any = false;
let mut alias_skipped = false;
let mut copy_skipped = false;
for (i, block) in blocks.iter_mut().enumerate() {
Expand Down Expand Up @@ -3606,8 +3607,21 @@ fn rewrite_yarn_classic(
result
.refused_yarn_classic_uuids
.insert(dep.patch_uuid.clone());
// The refusal leaves this entry as it was, so it carries a
// hosted pin only if an earlier run already wrote one — this
// run's URL, or one on the same patch server from an earlier
// grant token or patch uuid.
pinned_any |= resolved_re.find(block).is_some_and(|m| {
let url = m
.as_str()
.trim_start_matches("\n resolved \"")
.trim_end_matches('"');
let url = url.split_once('#').map_or(url, |(u, _)| u);
berry_hosted_pin_is_ours(url, &fname, Some(&dep.version), &dep.artifact_url)
});
continue;
}
pinned_any = true;
let frag = dep
.integrity
.sha1
Expand Down Expand Up @@ -3664,7 +3678,7 @@ fn rewrite_yarn_classic(
detail: format!("no yarn.lock entry resolving {fname}@{}", dep.version),
});
}
any_pinned |= matched_any;
any_pinned |= pinned_any;
}
// Yarn 2+ (berry) migrates a classic lock on install and re-resolves
// every entry from the registry, dropping the hosted pins this lock now
Expand Down Expand Up @@ -10127,6 +10141,62 @@ mod tests {
assert_eq!(berry_risk_count(&r), 0, "{:?}", r.warnings);
}

/// An offline-mirror refusal writes no pin, so it must not also claim
/// the lock carries hosted pins; a pin an earlier run already wrote is
/// still at risk and still warned about.
#[test]
fn yarn_classic_berry_risk_follows_pins_under_offline_mirror_refusal() {
let lp = npm_override(
"left-pad",
"1.3.0",
"http://p.test/lp.tgz",
"sha512-PATCHED==",
);
let mut files = classic_files(Some(r#"{"name":"p"}"#));
let mut first = RewriteResult::default();
rewrite_yarn_classic(&files, std::slice::from_ref(&lp), &mut first);
let pinned_lock = first.files["yarn.lock"].clone();

files.insert(
YARNRC_REL.to_string(),
"yarn-offline-mirror ./mirror\n".to_string(),
);
let mut r = RewriteResult::default();
rewrite_yarn_classic(&files, std::slice::from_ref(&lp), &mut r);
let codes: Vec<&str> = r.warnings.iter().map(|w| w.code.as_str()).collect();
assert_eq!(codes, ["redirect_yarn_classic_offline_mirror"]);

files.insert("yarn.lock".into(), pinned_lock);
let mut r = RewriteResult::default();
rewrite_yarn_classic(&files, std::slice::from_ref(&lp), &mut r);
assert!(r.files.is_empty(), "{:?}", r.files);
assert_eq!(berry_risk_count(&r), 1, "{:?}", r.warnings);

// A pin from an earlier grant token or uuid on the same patch server
// is still lost on a berry migrate, so it is still warned about; a
// same-named tarball on another origin (a user mirror) is not ours.
let host = "https://patch.test/patch/npm/left-pad/1.3.0";
let current = npm_override(
"left-pad",
"1.3.0",
&format!("{host}/tok-new/uuid-new/left-pad-1.3.0.tgz"),
"sha512-PATCHED==",
);
for (old_url, expect) in [
(format!("{host}/tok-old/uuid-old/left-pad-1.3.0.tgz"), 1),
("https://mirror.test/left-pad-1.3.0.tgz".to_string(), 0),
] {
let pinned = classic_lock_two_entries().replace(
"https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz",
&old_url,
);
files.insert("yarn.lock".into(), pinned);
let mut r = RewriteResult::default();
rewrite_yarn_classic(&files, std::slice::from_ref(&current), &mut r);
assert_eq!(berry_risk_count(&r), expect, "{old_url}: {:?}", r.warnings);
}
}

/// A CRLF classic lock (Windows `core.autocrlf` checkout) must rewrite
/// the TARGET entry, not whichever entry happens to come first, and every
/// untouched line must keep its CRLF ending byte-exactly (see the CRLF
Expand Down
Loading