Skip to content

Commit 4947219

Browse files
committed
Fix: Include --patch-server-url origins in attribution gate discovery
The attribution gate's unattributed_pins function was only using origins from candidates' deps, missing the --patch-server-url allowlist that management commands (vex, list, rollback, remove, vendor) use. This meant existing pins on configured extra origins were invisible to the gate when grants lived on another host, allowing contested writes that should be refused. Added patch_server_origins field to RewriteOptions and related types to pass the configured origins through to discovery, ensuring the gate uses the same origin allowlist as management commands.
1 parent e25c17d commit 4947219

120 files changed

Lines changed: 2213 additions & 797 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/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;
@@ -136,9 +136,11 @@ pub(crate) async fn hosted_state_from_lockfiles(
136136
common: &crate::args::GlobalArgs,
137137
root: &Path,
138138
) -> socket_patch_core::patch::redirect::RedirectState {
139-
hosted_state_from_pins(&socket_patch_core::patch::redirect::upstream::HostedPin::all(
140-
&discover_wiring(common, root).await,
141-
))
139+
hosted_state_from_pins(
140+
&socket_patch_core::patch::redirect::upstream::HostedPin::all(
141+
&discover_wiring(common, root).await,
142+
),
143+
)
142144
}
143145

144146
/// [`hosted_state_from_lockfiles`] over already-discovered pins. A purl
@@ -148,18 +150,17 @@ pub(crate) fn hosted_state_from_pins(
148150
) -> socket_patch_core::patch::redirect::RedirectState {
149151
let mut state = socket_patch_core::patch::redirect::RedirectState::new();
150152
for pin in pins {
151-
state
152-
.records
153-
.entry(pin.purl.clone())
154-
.or_insert_with(|| socket_patch_core::manifest::schema::PatchRecord {
153+
state.records.entry(pin.purl.clone()).or_insert_with(|| {
154+
socket_patch_core::manifest::schema::PatchRecord {
155155
uuid: pin.uuid.clone(),
156156
exported_at: String::new(),
157157
files: Default::default(),
158158
vulnerabilities: Default::default(),
159159
description: String::new(),
160160
license: String::new(),
161161
tier: String::new(),
162-
});
162+
}
163+
});
163164
}
164165
state
165166
}
@@ -186,4 +187,3 @@ pub(crate) fn vendor_state_lenient(
186187
}
187188
}
188189
}
189-

‎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());
@@ -1045,7 +1048,9 @@ mod tests {
10451048
..GlobalArgs::default()
10461049
};
10471050
let state = socket_patch_core::vendor::load_state(root).await;
1048-
vendored_ledger_supplement(&args, crawled, &state).await.packages
1051+
vendored_ledger_supplement(&args, crawled, &state)
1052+
.await
1053+
.packages
10491054
}
10501055

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

1083-
let out = vendored_ledger_supplement(&args, &[], &Ok(state)).await.packages;
1088+
let out = vendored_ledger_supplement(&args, &[], &Ok(state))
1089+
.await
1090+
.packages;
10841091
assert_eq!(
10851092
out.iter().map(|p| p.purl.as_str()).collect::<Vec<_>>(),
10861093
vec!["pkg:composer/psr/log@3.0.2.0"]
@@ -1183,7 +1190,10 @@ mod tests {
11831190
let state = npm_ledger_with_lock(tmp.path(), lock.as_deref()).await;
11841191
let out = vendored_ledger_supplement(&args, &[], &state).await;
11851192
assert_eq!(
1186-
out.packages.iter().map(|p| p.purl.as_str()).collect::<Vec<_>>(),
1193+
out.packages
1194+
.iter()
1195+
.map(|p| p.purl.as_str())
1196+
.collect::<Vec<_>>(),
11871197
vec!["pkg:npm/left-pad@1.3.0"],
11881198
"lock={lock:?}"
11891199
);

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

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,7 +1023,11 @@ pub(crate) async fn run_redirect_selected(
10231023
.map(|c| c.dep.patch_uuid.clone())
10241024
.collect()
10251025
};
1026-
let rewrite_options = || RewriteOptions {
1026+
// The `--patch-server-url` allowlist: extra patch-server origins the
1027+
// attribution gate recognizes when discovering attributable pins.
1028+
let patch_server_origins = crate::commands::rollback::patch_server_origins(common);
1029+
let rewrite_options = || {
1030+
RewriteOptions {
10271031
dry_run: common.dry_run,
10281032
targets_pipenv_lock,
10291033
pipenv_major,
@@ -1036,6 +1040,8 @@ pub(crate) async fn run_redirect_selected(
10361040
npm_outer: &npm_outer,
10371041
blocking: true,
10381042
takeover_uuids: takeover_uuids.clone(),
1043+
patch_server_origins: patch_server_origins.clone(),
1044+
}
10391045
};
10401046
// The rollout gate plans again without its deferred rows: keep what
10411047
// the second pass needs.
@@ -4787,19 +4793,43 @@ mod tests {
47874793
use super::npm_allow_remote_one_line;
47884794
let hosts = ["patch.socket.dev"];
47894795
let cases = [
4790-
(npm_allow_remote_configured_detail(&hosts, true, false), "Note: set"),
4791-
(npm_allow_remote_configured_detail(&hosts, false, false), "Note: set"),
4792-
(npm_allow_remote_configured_detail(&hosts, true, true), "Note: would set"),
4793-
(npm_allow_remote_already_detail(&hosts), "Note: .npmrc already"),
4794-
(npm_allow_remote_user_set_detail(&hosts, "none"), "Warning: npm >=12"),
4795-
(npm_allow_remote_env_set_detail(&hosts, "npm_config_allow_remote", "none"), "Warning: npm >=12"),
4796+
(
4797+
npm_allow_remote_configured_detail(&hosts, true, false),
4798+
"Note: set",
4799+
),
4800+
(
4801+
npm_allow_remote_configured_detail(&hosts, false, false),
4802+
"Note: set",
4803+
),
4804+
(
4805+
npm_allow_remote_configured_detail(&hosts, true, true),
4806+
"Note: would set",
4807+
),
4808+
(
4809+
npm_allow_remote_already_detail(&hosts),
4810+
"Note: .npmrc already",
4811+
),
4812+
(
4813+
npm_allow_remote_user_set_detail(&hosts, "none"),
4814+
"Warning: npm >=12",
4815+
),
4816+
(
4817+
npm_allow_remote_env_set_detail(&hosts, "npm_config_allow_remote", "none"),
4818+
"Warning: npm >=12",
4819+
),
47964820
(npm_allow_remote_manual_detail(&hosts), "Warning: npm >=12"),
4797-
(npm_allow_remote_unreadable_detail(&hosts, "is a symlink"), "Warning: npm >=12"),
4821+
(
4822+
npm_allow_remote_unreadable_detail(&hosts, "is a symlink"),
4823+
"Warning: npm >=12",
4824+
),
47984825
];
47994826
for (detail, start) in cases {
48004827
let line = npm_allow_remote_one_line(&detail);
48014828
assert!(line.starts_with(start), "{line}");
4802-
assert!(!line.contains('\n') && line.ends_with("(details: --verbose)."), "{line}");
4829+
assert!(
4830+
!line.contains('\n') && line.ends_with("(details: --verbose)."),
4831+
"{line}"
4832+
);
48034833
}
48044834
}
48054835
}

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

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ use socket_patch_core::api::ranking::cmp_search_results;
1111
use socket_patch_core::api::types::PatchSearchResult;
1212
use socket_patch_core::manifest::schema::PatchManifest;
1313
use socket_patch_core::policy::{
14-
canon, find_repo_root_with_warnings, policy_block, FilteredEntry, RetainedEntry, patch_severity_order, repo_relative_checked, sanitize, severity_name,
15-
DiskPolicyFs, FilterReason, Offers, PolicyError, PolicySource, PolicyWarning, Root, SelectionPolicy,
16-
PATCHES_DISABLED,
14+
canon, find_repo_root_with_warnings, patch_severity_order, policy_block, repo_relative_checked,
15+
sanitize, severity_name, DiskPolicyFs, FilterReason, FilteredEntry, Offers, PolicyError,
16+
PolicySource, PolicyWarning, RetainedEntry, Root, SelectionPolicy, PATCHES_DISABLED,
1717
};
1818
use socket_patch_core::utils::purl::normalize_purl;
1919

@@ -42,12 +42,18 @@ pub(crate) struct InvocationPolicy {
4242
/// Load the policy for `args` (4.5): `--global` scans have no repo and read
4343
/// no file; everything else reads the repo root's socket.yml.
4444
pub(crate) fn load_invocation_policy(args: &ScanArgs) -> Result<InvocationPolicy, PolicyLoadError> {
45-
let overrides = args.socket_yml.overrides().map_err(PolicyLoadError::Usage)?;
45+
let overrides = args
46+
.socket_yml
47+
.overrides()
48+
.map_err(PolicyLoadError::Usage)?;
4649
let cwd = std::fs::canonicalize(&args.common.cwd).unwrap_or_else(|_| args.common.cwd.clone());
4750
if args.common.is_global() {
48-
let policy = SelectionPolicy::load(&socket_patch_core::policy::MemoryPolicyFs::default(), &overrides)
49-
.map_err(PolicyLoadError::Policy)?
50-
.0;
51+
let policy = SelectionPolicy::load(
52+
&socket_patch_core::policy::MemoryPolicyFs::default(),
53+
&overrides,
54+
)
55+
.map_err(PolicyLoadError::Policy)?
56+
.0;
5157
return Ok(InvocationPolicy {
5258
policy,
5359
repo_root: cwd,
@@ -56,8 +62,8 @@ pub(crate) fn load_invocation_policy(args: &ScanArgs) -> Result<InvocationPolicy
5662
});
5763
}
5864
let (repo_root, mut warnings) = find_repo_root_with_warnings(&cwd);
59-
let (policy, load_warnings) =
60-
SelectionPolicy::load(&DiskPolicyFs::new(&repo_root), &overrides).map_err(PolicyLoadError::Policy)?;
65+
let (policy, load_warnings) = SelectionPolicy::load(&DiskPolicyFs::new(&repo_root), &overrides)
66+
.map_err(PolicyLoadError::Policy)?;
6167
warnings.extend(load_warnings);
6268
Ok(InvocationPolicy {
6369
policy,
@@ -141,7 +147,12 @@ pub(crate) struct ScanPolicy {
141147

142148
impl ScanPolicy {
143149
/// The policy for the project rooted at `root_dir`.
144-
pub(crate) fn for_root(invocation: &InvocationPolicy, root_dir: &Path, explicit: bool, global: bool) -> Self {
150+
pub(crate) fn for_root(
151+
invocation: &InvocationPolicy,
152+
root_dir: &Path,
153+
explicit: bool,
154+
global: bool,
155+
) -> Self {
145156
let root_dir = std::fs::canonicalize(root_dir).unwrap_or_else(|_| root_dir.to_path_buf());
146157
let project = repo_relative_checked(&invocation.repo_root, &root_dir).unwrap_or_default();
147158
let root_verdict = if global {
@@ -174,7 +185,9 @@ impl ScanPolicy {
174185
severity: None,
175186
});
176187
}
177-
let announce_warnings = !invocation.warned.swap(true, std::sync::atomic::Ordering::Relaxed);
188+
let announce_warnings = !invocation
189+
.warned
190+
.swap(true, std::sync::atomic::Ordering::Relaxed);
178191
Self {
179192
policy: invocation.policy.clone(),
180193
warnings,
@@ -227,7 +240,10 @@ impl ScanPolicy {
227240
/// exclude stays in the query (so `upgradeAvailable` can be reported)
228241
/// but joins the retained set, which never reaches a writer.
229242
pub(crate) fn admit_crawled(&self, purl: &str) -> bool {
230-
let verdict = self.root_verdict.clone().and_then(|()| self.policy.admits_purl(purl));
243+
let verdict = self
244+
.root_verdict
245+
.clone()
246+
.and_then(|()| self.policy.admits_purl(purl));
231247
let reason = match verdict {
232248
Ok(()) => return true,
233249
Err(reason) => reason,
@@ -337,7 +353,8 @@ impl ScanPolicy {
337353
// (not when a lower-ranked admitted patch simply wins).
338354
let top_withheld = self.policy.admits_severity(patch_severity_order(&group[0]));
339355
if let Err(reason) = top_withheld {
340-
let upgrade_withheld = chosen.is_some() && chosen == recorded_at && recorded_at != Some(0);
356+
let upgrade_withheld =
357+
chosen.is_some() && chosen == recorded_at && recorded_at != Some(0);
341358
if chosen.is_none() || upgrade_withheld {
342359
report.filtered.push(FilteredEntry {
343360
purl: Some(canon(&purl)),
@@ -525,17 +542,20 @@ pub(crate) fn policy_bypass_warnings(
525542
let verdict = if !policy.enabled() {
526543
Err(FilterReason::Disabled)
527544
} else {
528-
root_verdict.clone().and_then(|()| policy.admits_purl(purl)).and_then(|()| {
529-
// The floor only hides a package when none of its patches pass.
530-
match group
531-
.iter()
532-
.map(|p| policy.admits_severity(patch_severity_order(p)))
533-
.find(Result::is_ok)
534-
{
535-
Some(ok) => ok,
536-
None => policy.admits_severity(patch_severity_order(group[0])),
537-
}
538-
})
545+
root_verdict
546+
.clone()
547+
.and_then(|()| policy.admits_purl(purl))
548+
.and_then(|()| {
549+
// The floor only hides a package when none of its patches pass.
550+
match group
551+
.iter()
552+
.map(|p| policy.admits_severity(patch_severity_order(p)))
553+
.find(Result::is_ok)
554+
{
555+
Some(ok) => ok,
556+
None => policy.admits_severity(patch_severity_order(group[0])),
557+
}
558+
})
539559
};
540560
if let Err(reason) = verdict {
541561
out.push((

0 commit comments

Comments
 (0)