GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Fix: Run hosted safety refusals before withdrawn sweep · SocketDev/socket-patch@a58cd7f

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
  • crates
    • socket-patch-cli/src/commands/scan
      • mod.rs
    • socket-patch-core/src/hosted
      • governing_root.rs
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎crates/socket-patch-cli/src/commands/scan/mod.rs‎

Copy file name to clipboardExpand all lines: crates/socket-patch-cli/src/commands/scan/mod.rs
+77Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2576,8 +2576,85 @@ async fn run_scan(
25762576
// longer serves, before the rewrite below re-pins any replacement.
25772577
// Runs ahead of every "nothing found" exit: a package whose only patch
25782578
// was withdrawn has no search results at all.
2579+
//
2580+
// Safety refusals run BEFORE the restore: a symlink or governing-root
2581+
// layout that hosted mode refuses must not be mutated.
25792582
let withdrawn_sweep = if hosted && !args.common.is_global() && policy.writes_allowed() {
25802583
let pins = withdrawn::pins_in_scope(&hosted_pin_list, &all_purls);
2584+
2585+
// Check layout refusals for the ecosystems involved in the sweep,
2586+
// before any restore (the sweep's `restore_upstream` would mutate
2587+
// lockfiles, including an ancestor lock or symlink the refusals
2588+
// prevent).
2589+
if !pins.is_empty() {
2590+
use socket_patch_core::vendor::lock_inventory::ProjectView;
2591+
let view = ProjectView::Disk(&args.common.cwd);
2592+
2593+
// Extract ecosystems from pins.
2594+
let mut has_npm = false;
2595+
let mut has_cargo = false;
2596+
let mut has_pypi = false;
2597+
for pin in &pins {
2598+
match Ecosystem::from_purl(&pin.purl) {
2599+
Some(Ecosystem::Npm) => has_npm = true,
2600+
Some(Ecosystem::Cargo) => has_cargo = true,
2601+
Some(Ecosystem::Pypi) => has_pypi = true,
2602+
_ => {}
2603+
}
2604+
}
2605+
2606+
// Check bun.lockb symlink refusal (npm ecosystem).
2607+
if has_npm {
2608+
use socket_patch_core::hosted::engine;
2609+
use socket_patch_core::vendor::lock_inventory::bun_text_lock_drives;
2610+
let bun_text_drives = bun_text_lock_drives(&view);
2611+
if !bun_text_drives && view.is_symlink("bun.lockb") {
2612+
let refusal = engine::bun_lockb_symlink_refusal();
2613+
if args.common.json {
2614+
let mut env = scan_envelope(&args.common);
2615+
env.mark_error(EnvelopeError::new(&refusal.code, &refusal.message));
2616+
emit_scan(&env);
2617+
} else {
2618+
eprintln!("Error: {}", ui::sentence_case(&refusal.message));
2619+
}
2620+
return 1;
2621+
}
2622+
}
2623+
2624+
// Check governing-root refusals (cargo/pypi/npm ecosystems).
2625+
if has_cargo || has_pypi || has_npm {
2626+
use socket_patch_core::hosted::governing_root;
2627+
// Read trust_lockfile_config the way the main rewrite does.
2628+
let trust_env_off =
2629+
socket_patch_core::utils::pnpm_workspace::trust_lockfile_env_for(
2630+
&view,
2631+
|var| std::env::var(var).ok(),
2632+
)
2633+
.await
2634+
.is_some_and(|found| found.as_bool() != Some(true));
2635+
let trust_lockfile_config = !args.common.no_trust_lockfile_config && !trust_env_off;
2636+
2637+
if let Some(refusal) = governing_root::refusal_for_ecosystems(
2638+
&view,
2639+
has_cargo,
2640+
has_pypi,
2641+
has_npm,
2642+
trust_lockfile_config,
2643+
)
2644+
.await
2645+
{
2646+
if args.common.json {
2647+
let mut env = scan_envelope(&args.common);
2648+
env.mark_error(EnvelopeError::new(&refusal.code, &refusal.message));
2649+
emit_scan(&env);
2650+
} else {
2651+
eprintln!("Error: {}", ui::sentence_case(&refusal.message));
2652+
}
2653+
return 1;
2654+
}
2655+
}
2656+
}
2657+
25812658
Some(withdrawn::sweep(&args.common, &api_client, &pins).await)
25822659
} else {
25832660
None
Collapse file

‎crates/socket-patch-core/src/hosted/governing_root.rs‎

Copy file name to clipboardExpand all lines: crates/socket-patch-core/src/hosted/governing_root.rs
+34-3Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,20 +92,51 @@ pub async fn refusal(
9292
ProjectView::Snapshot(snap) => snap.root(),
9393
ProjectView::Memory(_) => return None,
9494
};
95-
if candidates.iter().any(|c| c.dep.ecosystem == "cargo") {
95+
let has_cargo = candidates.iter().any(|c| c.dep.ecosystem == "cargo");
96+
let has_pypi = candidates.iter().any(|c| c.dep.ecosystem == "pypi");
97+
let has_npm = candidates.iter().any(|c| c.dep.ecosystem == "npm");
98+
refusal_for_ecosystems_impl(root, has_cargo, has_pypi, has_npm, trust_lockfile_config).await
99+
}
100+
101+
/// Like [`refusal`], but takes ecosystem flags instead of candidates. Used by
102+
/// the withdrawn-pin sweep to check layout safety before any restore.
103+
pub async fn refusal_for_ecosystems(
104+
view: &ProjectView<'_>,
105+
has_cargo: bool,
106+
has_pypi: bool,
107+
has_npm: bool,
108+
trust_lockfile_config: bool,
109+
) -> Option<Refusal> {
110+
let root: &Path = match view {
111+
ProjectView::Disk(root) => root,
112+
ProjectView::Snapshot(snap) => snap.root(),
113+
ProjectView::Memory(_) => return None,
114+
};
115+
refusal_for_ecosystems_impl(root, has_cargo, has_pypi, has_npm, trust_lockfile_config).await
116+
}
117+
118+
/// Shared implementation for [`refusal`] and [`refusal_for_ecosystems`].
119+
async fn refusal_for_ecosystems_impl(
120+
root: &Path,
121+
has_cargo: bool,
122+
has_pypi: bool,
123+
has_npm: bool,
124+
trust_lockfile_config: bool,
125+
) -> Option<Refusal> {
126+
if has_cargo {
96127
if let Some(refusal) = cargo_member_refusal(root).await {
97128
return Some(refusal);
98129
}
99130
}
100-
if candidates.iter().any(|c| c.dep.ecosystem == "pypi") {
131+
if has_pypi {
101132
if let Some(workspace) = crate::utils::uv_workspace::governing_uv_workspace(root).await {
102133
return Some(Refusal {
103134
code: WORKSPACE_LOCKFILE_ELSEWHERE.to_string(),
104135
message: crate::utils::uv_workspace::member_detail(root, &workspace),
105136
});
106137
}
107138
}
108-
if candidates.iter().any(|c| c.dep.ecosystem == "npm") {
139+
if has_npm {
109140
let workspace = if has_own_npm_family_lock(root) {
110141
member_stray_lock_refusal(root).await
111142
} else {

0 commit comments

Comments
0 (0)
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.