Skip to content

Commit 62e896c

Browse files
committed
Fix: Refuse git dependencies in Yarn classic lock inventory
Yarn classic was incorrectly treating git HTTPS URLs (like https://github.com/user/repo.git#<commit>) as verifiable registry entries. This caused them to be deferred to the patch service instead of being refused as git dependencies that don't have registry artifacts. The fix: 1. Modified http_url() to reject URLs ending in .git (git repositories) 2. Updated Yarn classic to clear integrity when resolved is None due to a non-HTTP URL, matching npm's behavior and preventing standalone integrity fields on git+ blocks from being kept This prevents the service from installing npm-registry artifacts over git dependencies that the lock does not describe. Bug-Id: 3120e866-8986-4a9f-8d7e-78b8eae543b4
1 parent 9a10303 commit 62e896c

3 files changed

Lines changed: 52 additions & 7 deletions

File tree

‎crates/socket-patch-core/src/vendor/lock_inventory/mod.rs‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ pub(crate) mod npm_family;
6565
pub(crate) mod pnpm;
6666
pub(crate) mod pypi;
6767
pub(crate) mod recover;
68-
pub(crate) mod vlt;
6968
pub mod view;
69+
pub(crate) mod vlt;
7070
pub(crate) mod wired;
7171
pub(crate) mod yarn;
7272

@@ -365,7 +365,16 @@ fn dedup_prefer_integrity(raw: Vec<LockfileEntry>) -> Vec<LockfileEntry> {
365365
/// cannot reproduce; such entries stay listed for discovery but the fetch
366366
/// layer's integrity rule decides fetchability).
367367
fn http_url(raw: &str) -> Option<String> {
368-
(raw.starts_with("https://") || raw.starts_with("http://")).then(|| raw.to_string())
368+
if !(raw.starts_with("https://") || raw.starts_with("http://")) {
369+
return None;
370+
}
371+
// A `.git` suffix (with or without a `#fragment`) is a git repository,
372+
// not a tarball artifact the registry conventions serve.
373+
let before_fragment = raw.split('#').next().unwrap();
374+
if before_fragment.ends_with(".git") {
375+
return None;
376+
}
377+
Some(raw.to_string())
369378
}
370379

371380
/// ARCHITECTURE GUARD (module docs): each per-format file is laid out as

‎crates/socket-patch-core/src/vendor/lock_inventory/tests.rs‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,35 @@ async fn yarn_classic_git_resolution_fragment_is_not_an_integrity() {
907907
assert_eq!(e.integrity, LockIntegrity::None);
908908
}
909909

910+
/// An https URL ending in `.git` is a git repository, not a registry
911+
/// tarball: the `#<commit>` fragment is not a tarball sha1, and a
912+
/// standalone integrity field verifies nothing the registry serves.
913+
#[tokio::test]
914+
async fn yarn_classic_dot_git_https_url_is_not_a_registry_entry() {
915+
let tmp = tempfile::tempdir().unwrap();
916+
write(
917+
tmp.path(),
918+
"yarn.lock",
919+
"# yarn lockfile v1\n\n\
920+
\"https-git@1.0.0\":\n\
921+
\x20 version \"1.0.0\"\n\
922+
\x20 resolved \"https://github.com/o/https-git.git#0123456789abcdef0123456789abcdef01234567\"\n\
923+
\n\
924+
\"with-integrity@git+https://example.com/with-integrity.git\":\n\
925+
\x20 version \"2.0.0\"\n\
926+
\x20 resolved \"git+https://example.com/with-integrity.git#abc\"\n\
927+
\x20 integrity sha512-standaloneIntegrity==\"\n",
928+
)
929+
.await;
930+
let (_, entries) = inventory_npm_lock(tmp.path()).await.unwrap().unwrap();
931+
let e1 = entry(&entries, "https-git");
932+
assert_eq!(e1.resolved, None);
933+
assert_eq!(e1.integrity, LockIntegrity::None);
934+
let e2 = entry(&entries, "with-integrity");
935+
assert_eq!(e2.resolved, None);
936+
assert_eq!(e2.integrity, LockIntegrity::None);
937+
}
938+
910939
// ── yarn berry ────────────────────────────────────────────────────────
911940

912941
const YARN_BERRY: &str = "# This file is generated by running \"yarn install\" inside your project.

‎crates/socket-patch-core/src/vendor/lock_inventory/yarn.rs‎

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,8 @@ fn classic_registry_view(text: &str) -> Vec<LockfileEntry> {
141141
// `resolved "url#sha1hex"` — the fragment is the legacy verifier of
142142
// a registry tarball. A git resolution's fragment is a commit id,
143143
// which verifies nothing a registry fetch could download.
144-
let (resolved, sha1_hex) = match classic_field(&block.lines, "resolved") {
144+
let resolved_raw = classic_field(&block.lines, "resolved");
145+
let (resolved, sha1_hex) = match resolved_raw {
145146
Some(raw) => {
146147
let (url, sha1) = split_resolved_sha1(raw);
147148
match http_url(url) {
@@ -151,10 +152,16 @@ fn classic_registry_view(text: &str) -> Vec<LockfileEntry> {
151152
}
152153
None => (None, None),
153154
};
154-
let integrity = classic_field(&block.lines, "integrity")
155-
.map(|i| LockIntegrity::Sri(i.to_string()))
156-
.or(sha1_hex.map(LockIntegrity::Sha1Hex))
157-
.unwrap_or(LockIntegrity::None);
155+
// A non-registry resolution (`file:`, `git+…`, `.git`) records the
156+
// integrity of an artifact no registry serves: nothing a registry
157+
// fetch could verify against.
158+
let integrity = match (&resolved, resolved_raw) {
159+
(None, Some(_)) => LockIntegrity::None,
160+
_ => classic_field(&block.lines, "integrity")
161+
.map(|i| LockIntegrity::Sri(i.to_string()))
162+
.or(sha1_hex.map(LockIntegrity::Sha1Hex))
163+
.unwrap_or(LockIntegrity::None),
164+
};
158165
out.push(LockfileEntry::npm(name, version, resolved, integrity));
159166
}
160167
out

0 commit comments

Comments
 (0)