@@ -21,6 +21,7 @@ use std::path::{Path, PathBuf};
2121
2222use clap:: Args ;
2323use socket_patch_core:: api:: client:: ApiClient ;
24+ use socket_patch_core:: crawlers:: Ecosystem ;
2425use socket_patch_core:: manifest:: schema:: { PatchManifest , PatchRecord } ;
2526use socket_patch_core:: telemetry:: { track_vex_failed, track_vex_generated, TelemetryAuth } ;
2627use socket_patch_core:: vex:: verify:: { npm_manifest_after_hash, DEPENDENCY_NOT_INSTALLED } ;
@@ -327,6 +328,32 @@ impl VexGenError {
327328 }
328329}
329330
331+ /// Check whether a workspace VEX run should refuse Cargo patches: a
332+ /// `.cargo/config` / `config.toml` sits above the repository root (stat
333+ /// only; it is never read), and cargo merges it into the build, so it may
334+ /// replace crates.io with another source while VEX verifies the shared
335+ /// registry cache. Returns `None` for a global run, a run without Cargo
336+ /// patches or a project that is no Cargo project.
337+ fn cargo_config_outside_root_vex ( common : & GlobalArgs , manifest : & PatchManifest ) -> Option < String > {
338+ let cwd = & common. cwd ;
339+ if common. is_global ( )
340+ || !common. ecosystem_selected ( Ecosystem :: Cargo )
341+ || !manifest
342+ . patches
343+ . keys ( )
344+ . any ( |p| Ecosystem :: from_purl ( p) == Some ( Ecosystem :: Cargo ) )
345+ || !( cwd. join ( "Cargo.toml" ) . is_file ( ) || cwd. join ( "Cargo.lock" ) . is_file ( ) )
346+ {
347+ return None ;
348+ }
349+ socket_patch_core:: vendor:: cargo_config:: config_above_repo ( cwd) . map ( |why| {
350+ format ! (
351+ "{why}: it may replace crates.io with another source, so the registry copy VEX \
352+ would verify may not be what cargo builds"
353+ )
354+ } )
355+ }
356+
330357pub async fn run ( args : VexArgs ) -> i32 {
331358 apply_env_toggles ( & args. common ) ;
332359
@@ -682,6 +709,20 @@ async fn generate_vex(
682709 Err ( reason) => return Err ( fail ( common, params, "product_undetected" , reason) . await ) ,
683710 } ;
684711
712+ // A cargo config above the repository root is never read in a
713+ // workspace run; it may move crates.io elsewhere, so Cargo patches
714+ // are refused instead of verifying the registry cache blind (the same
715+ // gate `apply` runs).
716+ let cargo_config_refused = cargo_config_outside_root_vex ( common, manifest) ;
717+ if let Some ( ref why) = cargo_config_refused {
718+ note_warning ( warnings, common, "cargo_config_outside_root" , why. clone ( ) ) ;
719+ }
720+ let cargo_refused = |purl : & str | {
721+ cargo_config_refused. is_some ( )
722+ && purl. starts_with ( "pkg:cargo/" )
723+ && manifest. patches . contains_key ( purl)
724+ } ;
725+
685726 // The help text promises "PURL/identifier", so an arbitrary string is
686727 // accepted — but the OpenVEX spec types the product `@id` as an IRI, and
687728 // strict consumers (vexctl et al.) may reject or mis-key a bare name.
@@ -742,16 +783,20 @@ async fn generate_vex(
742783 applied : manifest
743784 . patches
744785 . keys ( )
745- . filter ( |purl| !mirror_refused ( purl) )
786+ . filter ( |purl| !mirror_refused ( purl) && ! cargo_refused ( purl ) )
746787 . cloned ( )
747788 . collect ( ) ,
748789 failed : manifest
749790 . patches
750791 . keys ( )
751- . filter ( |purl| mirror_refused ( purl) )
792+ . filter ( |purl| mirror_refused ( purl) || cargo_refused ( purl ) )
752793 . map ( |purl| FailedPatch {
753794 purl : purl. clone ( ) ,
754- reason : "mirror_overrides_source" . into ( ) ,
795+ reason : if mirror_refused ( purl) {
796+ "mirror_overrides_source" . into ( )
797+ } else {
798+ "cargo_config_outside_root" . into ( )
799+ } ,
755800 } )
756801 . collect ( ) ,
757802 vendored,
@@ -895,6 +940,24 @@ async fn generate_vex(
895940 outcome
896941 } ;
897942
943+ // A cargo config above the repository root moves any Cargo patches from
944+ // applied to failed: the crawler inspected the registry cache, but cargo
945+ // may build from elsewhere (a directory source). The same gate `apply`
946+ // runs.
947+ if cargo_config_refused. is_some ( ) {
948+ let cargo_purls: Vec < String > = outcome
949+ . applied
950+ . iter ( )
951+ . filter ( |purl| purl. starts_with ( "pkg:cargo/" ) )
952+ . cloned ( )
953+ . collect ( ) ;
954+ outcome. applied . retain ( |purl| !purl. starts_with ( "pkg:cargo/" ) ) ;
955+ outcome. failed . extend ( cargo_purls. into_iter ( ) . map ( |purl| FailedPatch {
956+ purl,
957+ reason : "cargo_config_outside_root" . into ( ) ,
958+ } ) ) ;
959+ }
960+
898961 // In-run `scan --mode hosted --vex`: the bytes of deps THAT RUN confirmed
899962 // redirected live on the patch server until the next install, so their
900963 // verification against the local tree would spuriously fail
@@ -2138,6 +2201,10 @@ fn omission_phrase(reason: &str) -> &'static str {
21382201 "the lockfiles wire this package to different patches, so which one the build \
21392202 installs cannot be determined"
21402203 }
2204+ "cargo_config_outside_root" => {
2205+ "a cargo config above the repository root may replace crates.io with another \
2206+ source, so the registry copy cannot be verified"
2207+ }
21412208 _ => "the patch could not be verified" ,
21422209 }
21432210}
0 commit comments