GeezerVison is showing github.com — Fix: Refuse git dependencies in Yarn classic lock inventory · SocketDev/socket-patch@62e896c
File treeExpand file treeCollapse file tree Open diff view settings crates/socket-patch-core/src/vendor/lock_inventory
Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: crates/socket-patch-core/src/vendor/lock_inventory/mod.rs +11-2Lines changed: 11 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -65,8 +65,8 @@ pub(crate) mod npm_family; | 65 | 65 | pub(crate) mod pnpm; | 66 | 66 | pub(crate) mod pypi; | 67 | 67 | pub(crate) mod recover; | 68 | | -pub(crate) mod vlt; | 69 | 68 | pub mod view; | | 69 | +pub(crate) mod vlt; | 70 | 70 | pub(crate) mod wired; | 71 | 71 | pub(crate) mod yarn; | 72 | 72 |
| @@ -365,7 +365,16 @@ fn dedup_prefer_integrity(raw: Vec<LockfileEntry>) -> Vec<LockfileEntry> { | 365 | 365 | /// cannot reproduce; such entries stay listed for discovery but the fetch | 366 | 366 | /// layer's integrity rule decides fetchability). | 367 | 367 | 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()) | 369 | 378 | } | 370 | 379 |
| 371 | 380 | /// ARCHITECTURE GUARD (module docs): each per-format file is laid out as | |
Collapse file Copy file name to clipboardExpand all lines: crates/socket-patch-core/src/vendor/lock_inventory/tests.rs +29Lines changed: 29 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -907,6 +907,35 @@ async fn yarn_classic_git_resolution_fragment_is_not_an_integrity() { | 907 | 907 | assert_eq!(e.integrity, LockIntegrity::None); | 908 | 908 | } | 909 | 909 |
| | 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 | + | | 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 | + | 910 | 939 | // ── yarn berry ──────────────────────────────────────────────────────── | 911 | 940 |
| 912 | 941 | const YARN_BERRY: &str = "# This file is generated by running \"yarn install\" inside your project. | |
Collapse file Copy file name to clipboardExpand all lines: crates/socket-patch-core/src/vendor/lock_inventory/yarn.rs +12-5Lines changed: 12 additions & 5 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -141,7 +141,8 @@ fn classic_registry_view(text: &str) -> Vec<LockfileEntry> { | 141 | 141 | // `resolved "url#sha1hex"` — the fragment is the legacy verifier of | 142 | 142 | // a registry tarball. A git resolution's fragment is a commit id, | 143 | 143 | // 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 { | 145 | 146 | Some(raw) => { | 146 | 147 | let (url, sha1) = split_resolved_sha1(raw); | 147 | 148 | match http_url(url) { | @@ -151,10 +152,16 @@ fn classic_registry_view(text: &str) -> Vec<LockfileEntry> { | 151 | 152 | } | 152 | 153 | None => (None, None), | 153 | 154 | }; | 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 | + }; | 158 | 165 | out.push(LockfileEntry::npm(name, version, resolved, integrity)); | 159 | 166 | } | 160 | 167 | out | |
|