Skip to content

Commit 896a700

Browse files
committed
Stop yarn berry pins leaking npm auth tokens
Hosted mode pinned a patched yarn berry package as an npm: locator (npm:<v>::__archiveUrl=<url>). Yarn fetches npm: locators with its npm fetcher, which attaches the configured registry token (npmAuthToken, YARN_NPM_AUTH_TOKEN, npmScopes) to every scoped package request, and to every request under npmAlwaysAuth, so the token was sent to the patch server on each cold install. The lock now pins a plain tarball-URL locator (<name>@<url>). Yarn fetches it with its tarball fetcher, which sends no registry auth and builds the same cache zip, so the 10c0 checksum is unchanged and --immutable still passes. Rollback, VEX and the mode takeovers keep recognizing the old form, and the next hosted scan re-pins it. An artifact URL yarn could not fetch as a tarball is refused instead of written. Fixes #404 Assisted-by: Claude Code:claude-opus-5-5
1 parent e26a1e8 commit 896a700

142 files changed

Lines changed: 2515 additions & 858 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.

‎CHANGELOG.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ limits, and required install commands.
113113
fetches honor `GOPROXY` and private-module settings.
114114
- Yarn Berry preserves supported line endings and checksum spellings. Mode
115115
preflights, including Bun's, run before discarding existing protection.
116+
- Yarn Berry hosted references no longer send npm registry credentials to the
117+
patch server. The lock now pins a plain tarball URL instead of an `npm:`
118+
locator, which made yarn attach `npmAuthToken` / `YARN_NPM_AUTH_TOKEN` to
119+
scoped packages (and to every package under `npmAlwaysAuth`). Locks pinned by
120+
earlier releases are re-pinned on the next hosted `scan`.
116121
- Composer hosted references remove upstream source fallbacks and mirrors;
117122
RubyGems hosted locks preserve source order; NuGet edits use the active config
118123
and survive `<clear />` entries.

‎crates/socket-patch-cli/CLI_CONTRACT.md‎

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

‎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;
@@ -97,9 +97,11 @@ pub(crate) async fn hosted_state_from_lockfiles(
9797
common: &crate::args::GlobalArgs,
9898
root: &Path,
9999
) -> socket_patch_core::patch::redirect::RedirectState {
100-
hosted_state_from_pins(&socket_patch_core::patch::redirect::upstream::HostedPin::all(
101-
&discover_wiring(common, root).await,
102-
))
100+
hosted_state_from_pins(
101+
&socket_patch_core::patch::redirect::upstream::HostedPin::all(
102+
&discover_wiring(common, root).await,
103+
),
104+
)
103105
}
104106

105107
/// [`hosted_state_from_lockfiles`] over already-discovered pins. A purl
@@ -109,18 +111,17 @@ pub(crate) fn hosted_state_from_pins(
109111
) -> socket_patch_core::patch::redirect::RedirectState {
110112
let mut state = socket_patch_core::patch::redirect::RedirectState::new();
111113
for pin in pins {
112-
state
113-
.records
114-
.entry(pin.purl.clone())
115-
.or_insert_with(|| socket_patch_core::manifest::schema::PatchRecord {
114+
state.records.entry(pin.purl.clone()).or_insert_with(|| {
115+
socket_patch_core::manifest::schema::PatchRecord {
116116
uuid: pin.uuid.clone(),
117117
exported_at: String::new(),
118118
files: Default::default(),
119119
vulnerabilities: Default::default(),
120120
description: String::new(),
121121
license: String::new(),
122122
tier: String::new(),
123-
});
123+
}
124+
});
124125
}
125126
state
126127
}
@@ -147,4 +148,3 @@ pub(crate) fn vendor_state_lenient(
147148
}
148149
}
149150
}
150-

‎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))
@@ -1157,7 +1158,11 @@ pub async fn run(args: RollbackArgs) -> i32 {
11571158
} else if !args.common.silent {
11581159
println!(
11591160
"{} the pre-v5 hosted ledger {}: no lockfile pins a hosted patch.",
1160-
if args.common.dry_run { "Would remove" } else { "Removed" },
1161+
if args.common.dry_run {
1162+
"Would remove"
1163+
} else {
1164+
"Removed"
1165+
},
11611166
socket_patch_core::patch::redirect::REDIRECT_STATE_REL
11621167
);
11631168
}

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

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -934,7 +934,8 @@ pub(crate) async fn run_redirect_selected(
934934
socket_patch_core::utils::fs::read_regular_to_string_sync(path).ok()
935935
})
936936
};
937-
let rewrite_options = || RewriteOptions {
937+
let rewrite_options = || {
938+
RewriteOptions {
938939
dry_run: common.dry_run,
939940
targets_pipenv_lock,
940941
pipenv_major,
@@ -946,6 +947,7 @@ pub(crate) async fn run_redirect_selected(
946947
npm_allow_remote_config: !common.no_npm_allow_remote_config,
947948
npm_outer: &npm_outer,
948949
blocking: true,
950+
}
949951
};
950952
// The rollout gate plans again without its deferred rows: keep what
951953
// the second pass needs.
@@ -2171,13 +2173,19 @@ fn join_names(names: &[String], max: usize) -> String {
21712173
/// artifacts, then verify with `vex`. After a vendored→hosted takeover
21722174
/// (`vendored_removed`) the commit also has to carry the deleted vendored
21732175
/// ledger entries and artifacts.
2174-
fn format_next_steps(files: &[String], edits: &[socket_patch_core::patch::redirect::FileEdit], vendored_removed: bool) -> Vec<String> {
2176+
fn format_next_steps(
2177+
files: &[String],
2178+
edits: &[socket_patch_core::patch::redirect::FileEdit],
2179+
vendored_removed: bool,
2180+
) -> Vec<String> {
21752181
if files.is_empty() && !vendored_removed {
21762182
return Vec::new();
21772183
}
21782184
let mut commit: Vec<String> = Vec::new();
21792185
if vendored_removed {
2180-
commit.push(".socket/vendor/ (the removed vendored ledger entries and artifacts)".to_string());
2186+
commit.push(
2187+
".socket/vendor/ (the removed vendored ledger entries and artifacts)".to_string(),
2188+
);
21812189
}
21822190
commit.extend(files.iter().cloned());
21832191
let npm = files
@@ -4095,19 +4103,43 @@ mod tests {
40954103
use super::npm_allow_remote_one_line;
40964104
let hosts = ["patch.socket.dev"];
40974105
let cases = [
4098-
(npm_allow_remote_configured_detail(&hosts, true, false), "Note: set"),
4099-
(npm_allow_remote_configured_detail(&hosts, false, false), "Note: set"),
4100-
(npm_allow_remote_configured_detail(&hosts, true, true), "Note: would set"),
4101-
(npm_allow_remote_already_detail(&hosts), "Note: .npmrc already"),
4102-
(npm_allow_remote_user_set_detail(&hosts, "none"), "Warning: npm >=12"),
4103-
(npm_allow_remote_env_set_detail(&hosts, "npm_config_allow_remote", "none"), "Warning: npm >=12"),
4106+
(
4107+
npm_allow_remote_configured_detail(&hosts, true, false),
4108+
"Note: set",
4109+
),
4110+
(
4111+
npm_allow_remote_configured_detail(&hosts, false, false),
4112+
"Note: set",
4113+
),
4114+
(
4115+
npm_allow_remote_configured_detail(&hosts, true, true),
4116+
"Note: would set",
4117+
),
4118+
(
4119+
npm_allow_remote_already_detail(&hosts),
4120+
"Note: .npmrc already",
4121+
),
4122+
(
4123+
npm_allow_remote_user_set_detail(&hosts, "none"),
4124+
"Warning: npm >=12",
4125+
),
4126+
(
4127+
npm_allow_remote_env_set_detail(&hosts, "npm_config_allow_remote", "none"),
4128+
"Warning: npm >=12",
4129+
),
41044130
(npm_allow_remote_manual_detail(&hosts), "Warning: npm >=12"),
4105-
(npm_allow_remote_unreadable_detail(&hosts, "is a symlink"), "Warning: npm >=12"),
4131+
(
4132+
npm_allow_remote_unreadable_detail(&hosts, "is a symlink"),
4133+
"Warning: npm >=12",
4134+
),
41064135
];
41074136
for (detail, start) in cases {
41084137
let line = npm_allow_remote_one_line(&detail);
41094138
assert!(line.starts_with(start), "{line}");
4110-
assert!(!line.contains('\n') && line.ends_with("(details: --verbose)."), "{line}");
4139+
assert!(
4140+
!line.contains('\n') && line.ends_with("(details: --verbose)."),
4141+
"{line}"
4142+
);
41114143
}
41124144
}
41134145
}

0 commit comments

Comments
 (0)