Skip to content

Commit a9d1c65

Browse files
committed
Fix hosted takeover encoding refusal timing
Move the undecodable candidate file check to before the vendored→hosted takeover writes. Previously, the encoding refusal lived only in engine::guard, which runs after vendored_takeover has already reverted lockfiles and dropped ledger rows. A UTF-16 requirements.txt now refuses the whole pypi run before any takeover writes, ensuring 'nothing was written' stays true. The fix reads candidate files before the takeover, checks for encoding issues in takeover-capable candidates' ecosystem files, and refuses before any writes if found. After the takeover, files are re-read only if the takeover modified them. Fixes bug d5bbed62-df74-46fe-8711-89795caa75d7
1 parent 5444dd6 commit a9d1c65

131 files changed

Lines changed: 2271 additions & 853 deletions

File tree

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/apply.rs‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ use clap::Args;
22
use socket_patch_core::api::blob_fetcher::get_missing_blobs;
33
use socket_patch_core::api::client::{get_api_client_with_overrides, ApiClient};
44
use socket_patch_core::crawlers::ruby_crawler::config_path_ignored_warning;
5-
use socket_patch_core::crawlers::{
6-
detect_npm_pkg_manager, Ecosystem, NpmPkgManager, RubyCrawler,
7-
};
5+
use socket_patch_core::crawlers::{detect_npm_pkg_manager, Ecosystem, NpmPkgManager, RubyCrawler};
86
use socket_patch_core::manifest::operations::read_manifest;
97
use socket_patch_core::manifest::schema::{PatchFileInfo, PatchManifest, PatchRecord};
108
use socket_patch_core::patch::apply::{

‎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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ use super::rollback::{
1717
pin_before_hash_blobs, rollback_patches_inner, run_hosted_leg, sweep_failure,
1818
sweep_unused_artifacts, HostedLegOutcome, InnerSelection,
1919
};
20-
use crate::commands::vendored_backend::{RevertedEntry, VendorRevertStep, VendoredBackend};
2120
use crate::args::{apply_env_toggles, GlobalArgs};
2221
use crate::commands::lock_cli::acquire_or_emit;
22+
use crate::commands::vendored_backend::{RevertedEntry, VendorRevertStep, VendoredBackend};
2323
use crate::json_envelope::{Command, Envelope, EnvelopeError, PatchAction, PatchEvent, Status};
2424
use crate::ui::plural;
2525

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ use socket_patch_core::manifest::operations::{
1010
};
1111
use socket_patch_core::manifest::schema::{PatchFileInfo, PatchManifest, PatchRecord};
1212
use socket_patch_core::patch::apply::select_installed_variants;
13+
use socket_patch_core::patch::redirect::upstream::HostedPin;
1314
use socket_patch_core::patch::rollback::{
1415
cannot_rollback_error, rollback_package_patch, verify_file_rollback, RollbackResult,
1516
VerifyRollbackResult, VerifyRollbackStatus,
1617
};
1718
use socket_patch_core::telemetry::{track_patch_rollback_failed, track_patch_rolled_back};
1819
use socket_patch_core::utils::purl::{patch_matches, strip_purl_qualifiers};
19-
use socket_patch_core::patch::redirect::upstream::HostedPin;
2020
use socket_patch_core::vendor::{purl_keys_cover, RevertOpts, VendorState};
2121
use std::collections::{HashMap, HashSet};
2222
use std::path::{Path, PathBuf};
@@ -1026,7 +1026,8 @@ pub(crate) async fn run_hosted_leg(common: &GlobalArgs, pins: &[HostedPin]) -> H
10261026
.iter()
10271027
.map(|(code, detail)| (code.to_string(), detail.clone())),
10281028
);
1029-
out.edited_files.extend(outcome.reverted_files.iter().cloned());
1029+
out.edited_files
1030+
.extend(outcome.reverted_files.iter().cloned());
10301031
let unwound: Vec<_> = vlt_targets
10311032
.into_iter()
10321033
.filter(|t| out.reverted.iter().any(|p| p == &t.purl))
@@ -1170,7 +1171,11 @@ pub async fn run(args: RollbackArgs) -> i32 {
11701171
} else if !args.common.silent {
11711172
println!(
11721173
"{} the pre-v5 hosted ledger {}: no lockfile pins a hosted patch.",
1173-
if args.common.dry_run { "Would remove" } else { "Removed" },
1174+
if args.common.dry_run {
1175+
"Would remove"
1176+
} else {
1177+
"Removed"
1178+
},
11741179
socket_patch_core::patch::redirect::REDIRECT_STATE_REL
11751180
);
11761181
}

‎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());
@@ -1038,7 +1041,9 @@ mod tests {
10381041
..GlobalArgs::default()
10391042
};
10401043
let state = socket_patch_core::vendor::load_state(root).await;
1041-
vendored_ledger_supplement(&args, crawled, &state).await.packages
1044+
vendored_ledger_supplement(&args, crawled, &state)
1045+
.await
1046+
.packages
10421047
}
10431048

10441049
/// A ledger entry vendored as `@3.0.2.0` is the crawled composer
@@ -1073,7 +1078,9 @@ mod tests {
10731078
out.iter().map(|p| &p.purl).collect::<Vec<_>>()
10741079
);
10751080

1076-
let out = vendored_ledger_supplement(&args, &[], &Ok(state)).await.packages;
1081+
let out = vendored_ledger_supplement(&args, &[], &Ok(state))
1082+
.await
1083+
.packages;
10771084
assert_eq!(
10781085
out.iter().map(|p| p.purl.as_str()).collect::<Vec<_>>(),
10791086
vec!["pkg:composer/psr/log@3.0.2.0"]
@@ -1176,7 +1183,10 @@ mod tests {
11761183
let state = npm_ledger_with_lock(tmp.path(), lock.as_deref()).await;
11771184
let out = vendored_ledger_supplement(&args, &[], &state).await;
11781185
assert_eq!(
1179-
out.packages.iter().map(|p| p.purl.as_str()).collect::<Vec<_>>(),
1186+
out.packages
1187+
.iter()
1188+
.map(|p| p.purl.as_str())
1189+
.collect::<Vec<_>>(),
11801190
vec!["pkg:npm/left-pad@1.3.0"],
11811191
"lock={lock:?}"
11821192
);

0 commit comments

Comments
 (0)