Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions crates/socket-patch-cli/src/commands/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,10 @@ pub async fn run(args: ListArgs) -> i32 {
detail: detail.clone(),
});
} else if !args.common.silent {
eprintln!("Warning: {}", crate::commands::rollback::capitalize_first(detail));
eprintln!(
"Warning: {}",
crate::commands::rollback::capitalize_first(detail)
);
}
}
let vendor_state = crate::commands::vendor_state_lenient(&loaded.vendor, args.common.silent);
Expand Down Expand Up @@ -773,12 +776,18 @@ mod tests {
let listings = HostedListing::from_pins(
&[
pin("pkg:npm/minimist@1.2.2", &record.uuid),
pin("pkg:npm/other@1.0.0", "33333333-3333-4333-8333-333333333333"),
pin(
"pkg:npm/other@1.0.0",
"33333333-3333-4333-8333-333333333333",
),
],
Some(&legacy),
);
assert_eq!(listings[0].record, record);
assert_eq!(listings[1].record.uuid, "33333333-3333-4333-8333-333333333333");
assert_eq!(
listings[1].record.uuid,
"33333333-3333-4333-8333-333333333333"
);
assert!(listings[1].record.vulnerabilities.is_empty());
assert_eq!(listings[1].lockfiles, vec!["yarn.lock".to_string()]);
}
Expand Down
22 changes: 11 additions & 11 deletions crates/socket-patch-cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
pub mod apply;
pub(crate) mod bun_preflight;
pub(crate) mod context;
pub(crate) mod composer_hints;
pub(crate) mod context;
pub(crate) mod fetch_stage;
pub mod get;
pub mod hosted_bundle;
pub mod list;
pub(crate) mod lock_cli;
pub mod remove;
pub mod repair;
pub(crate) mod vendored_backend;
pub mod rollback;
pub mod scan;
pub mod update;
pub mod vendor;
pub(crate) mod vendored_backend;
pub mod vex;
pub(crate) mod vex_consumed;
pub(crate) mod vex_sources;
Expand Down Expand Up @@ -141,9 +141,11 @@ pub(crate) async fn hosted_state_from_lockfiles(
common: &crate::args::GlobalArgs,
root: &Path,
) -> socket_patch_core::patch::redirect::RedirectState {
hosted_state_from_pins(&socket_patch_core::patch::redirect::upstream::HostedPin::all(
&discover_wiring(common, root).await,
))
hosted_state_from_pins(
&socket_patch_core::patch::redirect::upstream::HostedPin::all(
&discover_wiring(common, root).await,
),
)
}

/// [`hosted_state_from_lockfiles`] over already-discovered pins. A purl
Expand All @@ -153,18 +155,17 @@ pub(crate) fn hosted_state_from_pins(
) -> socket_patch_core::patch::redirect::RedirectState {
let mut state = socket_patch_core::patch::redirect::RedirectState::new();
for pin in pins {
state
.records
.entry(pin.purl.clone())
.or_insert_with(|| socket_patch_core::manifest::schema::PatchRecord {
state.records.entry(pin.purl.clone()).or_insert_with(|| {
socket_patch_core::manifest::schema::PatchRecord {
uuid: pin.uuid.clone(),
exported_at: String::new(),
files: Default::default(),
vulnerabilities: Default::default(),
description: String::new(),
license: String::new(),
tier: String::new(),
});
}
});
}
state
}
Expand All @@ -191,4 +192,3 @@ pub(crate) fn vendor_state_lenient(
}
}
}

62 changes: 36 additions & 26 deletions crates/socket-patch-cli/src/commands/scan/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,29 +168,32 @@ pub(crate) async fn vendored_ledger_supplement(
}
// `(ledger key, base purl, entry)`; the artifact fallback has no
// entries to probe, so it never reports unwired keys.
let candidates: Vec<(String, String, Option<&socket_patch_core::vendor::VendorEntry>)> =
match state {
Ok(state) => state
.entries
.iter()
.map(|(key, entry)| {
(
key.clone(),
strip_purl_qualifiers(&entry.base_purl).to_string(),
Some(entry),
)
})
.collect(),
// Corrupt/unreadable ledger (a MISSING file is Ok(empty) above):
// recover the vendored set from the committed artifacts, or
// `scan --prune` (whose ledger exemption also degrades to empty)
// would delete still-vendored packages' manifest entries and blobs.
Err(_) => vendored_purls_from_artifacts(common)
.await
.into_iter()
.map(|base| (base.clone(), base, None))
.collect(),
};
let candidates: Vec<(
String,
String,
Option<&socket_patch_core::vendor::VendorEntry>,
)> = match state {
Ok(state) => state
.entries
.iter()
.map(|(key, entry)| {
(
key.clone(),
strip_purl_qualifiers(&entry.base_purl).to_string(),
Some(entry),
)
})
.collect(),
// Corrupt/unreadable ledger (a MISSING file is Ok(empty) above):
// recover the vendored set from the committed artifacts, or
// `scan --prune` (whose ledger exemption also degrades to empty)
// would delete still-vendored packages' manifest entries and blobs.
Err(_) => vendored_purls_from_artifacts(common)
.await
.into_iter()
.map(|base| (base.clone(), base, None))
.collect(),
};
// Composer by release identity: a ledger `@3.0.2.0` is the crawled
// `@3.0.2`, not a second package to supplement.
let key = |p: &str| composer_purl_identity(p).unwrap_or_else(|| normalize_purl(p).into_owned());
Expand Down Expand Up @@ -1045,7 +1048,9 @@ mod tests {
..GlobalArgs::default()
};
let state = socket_patch_core::vendor::load_state(root).await;
vendored_ledger_supplement(&args, crawled, &state).await.packages
vendored_ledger_supplement(&args, crawled, &state)
.await
.packages
}

/// A ledger entry vendored as `@3.0.2.0` is the crawled composer
Expand Down Expand Up @@ -1080,7 +1085,9 @@ mod tests {
out.iter().map(|p| &p.purl).collect::<Vec<_>>()
);

let out = vendored_ledger_supplement(&args, &[], &Ok(state)).await.packages;
let out = vendored_ledger_supplement(&args, &[], &Ok(state))
.await
.packages;
assert_eq!(
out.iter().map(|p| p.purl.as_str()).collect::<Vec<_>>(),
vec!["pkg:composer/psr/log@3.0.2.0"]
Expand Down Expand Up @@ -1183,7 +1190,10 @@ mod tests {
let state = npm_ledger_with_lock(tmp.path(), lock.as_deref()).await;
let out = vendored_ledger_supplement(&args, &[], &state).await;
assert_eq!(
out.packages.iter().map(|p| p.purl.as_str()).collect::<Vec<_>>(),
out.packages
.iter()
.map(|p| p.purl.as_str())
.collect::<Vec<_>>(),
vec!["pkg:npm/left-pad@1.3.0"],
"lock={lock:?}"
);
Expand Down
44 changes: 35 additions & 9 deletions crates/socket-patch-cli/src/commands/scan/hosted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -987,7 +987,8 @@ pub(crate) async fn run_redirect_selected(
socket_patch_core::utils::fs::read_regular_to_string_sync(path).ok()
})
};
let rewrite_options = || RewriteOptions {
let rewrite_options = || {
RewriteOptions {
dry_run: common.dry_run,
targets_pipenv_lock,
pipenv_major,
Expand All @@ -999,6 +1000,7 @@ pub(crate) async fn run_redirect_selected(
npm_allow_remote_config: !common.no_npm_allow_remote_config,
npm_outer: &npm_outer,
blocking: true,
}
};
// The rollout gate plans again without its deferred rows: keep what
// the second pass needs.
Expand Down Expand Up @@ -4746,19 +4748,43 @@ mod tests {
use super::npm_allow_remote_one_line;
let hosts = ["patch.socket.dev"];
let cases = [
(npm_allow_remote_configured_detail(&hosts, true, false), "Note: set"),
(npm_allow_remote_configured_detail(&hosts, false, false), "Note: set"),
(npm_allow_remote_configured_detail(&hosts, true, true), "Note: would set"),
(npm_allow_remote_already_detail(&hosts), "Note: .npmrc already"),
(npm_allow_remote_user_set_detail(&hosts, "none"), "Warning: npm >=12"),
(npm_allow_remote_env_set_detail(&hosts, "npm_config_allow_remote", "none"), "Warning: npm >=12"),
(
npm_allow_remote_configured_detail(&hosts, true, false),
"Note: set",
),
(
npm_allow_remote_configured_detail(&hosts, false, false),
"Note: set",
),
(
npm_allow_remote_configured_detail(&hosts, true, true),
"Note: would set",
),
(
npm_allow_remote_already_detail(&hosts),
"Note: .npmrc already",
),
(
npm_allow_remote_user_set_detail(&hosts, "none"),
"Warning: npm >=12",
),
(
npm_allow_remote_env_set_detail(&hosts, "npm_config_allow_remote", "none"),
"Warning: npm >=12",
),
(npm_allow_remote_manual_detail(&hosts), "Warning: npm >=12"),
(npm_allow_remote_unreadable_detail(&hosts, "is a symlink"), "Warning: npm >=12"),
(
npm_allow_remote_unreadable_detail(&hosts, "is a symlink"),
"Warning: npm >=12",
),
];
for (detail, start) in cases {
let line = npm_allow_remote_one_line(&detail);
assert!(line.starts_with(start), "{line}");
assert!(!line.contains('\n') && line.ends_with("(details: --verbose)."), "{line}");
assert!(
!line.contains('\n') && line.ends_with("(details: --verbose)."),
"{line}"
);
}
}
}
68 changes: 44 additions & 24 deletions crates/socket-patch-cli/src/commands/scan/policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ use socket_patch_core::api::ranking::cmp_search_results;
use socket_patch_core::api::types::PatchSearchResult;
use socket_patch_core::manifest::schema::PatchManifest;
use socket_patch_core::policy::{
canon, find_repo_root_with_warnings, policy_block, FilteredEntry, RetainedEntry, patch_severity_order, repo_relative_checked, sanitize, severity_name,
DiskPolicyFs, FilterReason, Offers, PolicyError, PolicySource, PolicyWarning, Root, SelectionPolicy,
PATCHES_DISABLED,
canon, find_repo_root_with_warnings, patch_severity_order, policy_block, repo_relative_checked,
sanitize, severity_name, DiskPolicyFs, FilterReason, FilteredEntry, Offers, PolicyError,
PolicySource, PolicyWarning, RetainedEntry, Root, SelectionPolicy, PATCHES_DISABLED,
};
use socket_patch_core::utils::purl::normalize_purl;

Expand Down Expand Up @@ -42,12 +42,18 @@ pub(crate) struct InvocationPolicy {
/// Load the policy for `args` (4.5): `--global` scans have no repo and read
/// no file; everything else reads the repo root's socket.yml.
pub(crate) fn load_invocation_policy(args: &ScanArgs) -> Result<InvocationPolicy, PolicyLoadError> {
let overrides = args.socket_yml.overrides().map_err(PolicyLoadError::Usage)?;
let overrides = args
.socket_yml
.overrides()
.map_err(PolicyLoadError::Usage)?;
let cwd = std::fs::canonicalize(&args.common.cwd).unwrap_or_else(|_| args.common.cwd.clone());
if args.common.is_global() {
let policy = SelectionPolicy::load(&socket_patch_core::policy::MemoryPolicyFs::default(), &overrides)
.map_err(PolicyLoadError::Policy)?
.0;
let policy = SelectionPolicy::load(
&socket_patch_core::policy::MemoryPolicyFs::default(),
&overrides,
)
.map_err(PolicyLoadError::Policy)?
.0;
return Ok(InvocationPolicy {
policy,
repo_root: cwd,
Expand All @@ -56,8 +62,8 @@ pub(crate) fn load_invocation_policy(args: &ScanArgs) -> Result<InvocationPolicy
});
}
let (repo_root, mut warnings) = find_repo_root_with_warnings(&cwd);
let (policy, load_warnings) =
SelectionPolicy::load(&DiskPolicyFs::new(&repo_root), &overrides).map_err(PolicyLoadError::Policy)?;
let (policy, load_warnings) = SelectionPolicy::load(&DiskPolicyFs::new(&repo_root), &overrides)
.map_err(PolicyLoadError::Policy)?;
warnings.extend(load_warnings);
Ok(InvocationPolicy {
policy,
Expand Down Expand Up @@ -141,7 +147,12 @@ pub(crate) struct ScanPolicy {

impl ScanPolicy {
/// The policy for the project rooted at `root_dir`.
pub(crate) fn for_root(invocation: &InvocationPolicy, root_dir: &Path, explicit: bool, global: bool) -> Self {
pub(crate) fn for_root(
invocation: &InvocationPolicy,
root_dir: &Path,
explicit: bool,
global: bool,
) -> Self {
let root_dir = std::fs::canonicalize(root_dir).unwrap_or_else(|_| root_dir.to_path_buf());
let project = repo_relative_checked(&invocation.repo_root, &root_dir).unwrap_or_default();
let root_verdict = if global {
Expand Down Expand Up @@ -174,7 +185,9 @@ impl ScanPolicy {
severity: None,
});
}
let announce_warnings = !invocation.warned.swap(true, std::sync::atomic::Ordering::Relaxed);
let announce_warnings = !invocation
.warned
.swap(true, std::sync::atomic::Ordering::Relaxed);
Self {
policy: invocation.policy.clone(),
warnings,
Expand Down Expand Up @@ -227,7 +240,10 @@ impl ScanPolicy {
/// exclude stays in the query (so `upgradeAvailable` can be reported)
/// but joins the retained set, which never reaches a writer.
pub(crate) fn admit_crawled(&self, purl: &str) -> bool {
let verdict = self.root_verdict.clone().and_then(|()| self.policy.admits_purl(purl));
let verdict = self
.root_verdict
.clone()
.and_then(|()| self.policy.admits_purl(purl));
let reason = match verdict {
Ok(()) => return true,
Err(reason) => reason,
Expand Down Expand Up @@ -337,7 +353,8 @@ impl ScanPolicy {
// (not when a lower-ranked admitted patch simply wins).
let top_withheld = self.policy.admits_severity(patch_severity_order(&group[0]));
if let Err(reason) = top_withheld {
let upgrade_withheld = chosen.is_some() && chosen == recorded_at && recorded_at != Some(0);
let upgrade_withheld =
chosen.is_some() && chosen == recorded_at && recorded_at != Some(0);
if chosen.is_none() || upgrade_withheld {
report.filtered.push(FilteredEntry {
purl: Some(canon(&purl)),
Expand Down Expand Up @@ -525,17 +542,20 @@ pub(crate) fn policy_bypass_warnings(
let verdict = if !policy.enabled() {
Err(FilterReason::Disabled)
} else {
root_verdict.clone().and_then(|()| policy.admits_purl(purl)).and_then(|()| {
// The floor only hides a package when none of its patches pass.
match group
.iter()
.map(|p| policy.admits_severity(patch_severity_order(p)))
.find(Result::is_ok)
{
Some(ok) => ok,
None => policy.admits_severity(patch_severity_order(group[0])),
}
})
root_verdict
.clone()
.and_then(|()| policy.admits_purl(purl))
.and_then(|()| {
// The floor only hides a package when none of its patches pass.
match group
.iter()
.map(|p| policy.admits_severity(patch_severity_order(p)))
.find(Result::is_ok)
{
Some(ok) => ok,
None => policy.admits_severity(patch_severity_order(group[0])),
}
})
};
if let Err(reason) = verdict {
out.push((
Expand Down
Loading
Loading