From 616e5ef7869663a436988cfa11f58c371a9c48dc Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 23:07:13 +0000 Subject: [PATCH 1/4] Keep hosted NuGet source after Hosted mode inserted the Socket package source (and, when the config already had a packageSourceMapping, its mapping) directly after the section's open tag. A config that starts the section with , common in corporate setups, then discarded the Socket entry, so dotnet restore failed NU1100 / NU1101 for the patched package. The entries now land after the last in their section. Assisted-by: claude-code:claude-opus-5-5 --- CHANGELOG.md | 7 ++ .../src/patch/redirect/mod.rs | 74 +++++++++++++++++-- 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c905badf..23a54194 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -914,6 +914,13 @@ into the new version's section — see docs/releasing.md. ### Fixed +- **Hosted nuget redirects survive a `` in `nuget.config`.** The + Socket source (and, in an existing ``, its + mapping) was inserted ahead of the section's ``, which NuGet + applies to everything read before it: `dotnet restore` then failed + NU1100 / NU1101 for the patched package. Both now land after the last + ``. + - **`rollback` fetches a before-blob that only a store peer variant needs.** The before-blob gate now probes every pnpm and vlt store variant copy the rollback restores, so an online rollback no longer fails diff --git a/crates/socket-patch-core/src/patch/redirect/mod.rs b/crates/socket-patch-core/src/patch/redirect/mod.rs index ef1b56cb..2a95eaef 100644 --- a/crates/socket-patch-core/src/patch/redirect/mod.rs +++ b/crates/socket-patch-core/src/patch/redirect/mod.rs @@ -5173,11 +5173,11 @@ fn add_nuget_source(config: &str, reg: &str, index_url: &str, pkg_id: &str) -> O // A mapping already exists (e.g. a prior patched dep, or the project's // own): append ONLY this source's mapping — every other source is // already covered. - out = out.replacen( - "", - &format!("\n{socket_mapping}"), - 1, - ); + // After any `` in the section: NuGet drops every mapping + // read before one, leaving the patched id routed nowhere. + let open_end = out.find("")? + "".len(); + let at = nuget_after_last_clear(&out, open_end, ""); + out = format!("{}\n{socket_mapping}{}", &out[..at], &out[at..]); } else { // Creating the mapping from scratch. Once ANY // exists, NuGet requires EVERY package to match some source's pattern, @@ -5249,7 +5249,9 @@ fn insert_nuget_source(config: &str, key: &str, url: &str) -> Option { // through to the from-scratch branch rather than insert outside it. .filter(|m| !m.as_str().ends_with("/>")) { - let end = m.end(); + // After any ``: NuGet drops every source read before one, + // so the mapping would point at an undefined source (NU1100). + let end = nuget_after_last_clear(config, m.end(), ""); Some(format!( "{}\n{source_line}{}", &config[..end], @@ -5271,6 +5273,20 @@ fn insert_nuget_source(config: &str, key: &str, url: &str) -> Option { } } +/// The offset just past the last `` between `from` and the next +/// `close` tag, else `from`. +fn nuget_after_last_clear(config: &str, from: usize, close: &str) -> usize { + static CLEAR_RE: LazyLock = + LazyLock::new(|| Regex::new(r"").expect("static clear-tag regex is valid")); + let Some(len) = config[from..].find(close) else { + return from; + }; + CLEAR_RE + .find_iter(&config[from..from + len]) + .last() + .map_or(from, |m| from + m.end()) +} + /// The `key` of every `` under `` (empty when there /// is no such element). Used to preserve resolution for non-patched packages /// when a `` is introduced. @@ -8275,6 +8291,52 @@ mod tests { ); } + /// NuGet's `` drops every item read before it: a Socket source + /// or mapping inserted ahead of one vanishes and restore NU1100s. + #[test] + fn nuget_socket_entries_land_after_clear() { + let mut files = BTreeMap::new(); + files.insert( + "nuget.config".to_string(), + "\n\n \n \n \n \n\n" + .to_string(), + ); + let r = rewrite_registry_redirect(&files, &[nuget_override()]); + let out = r.files.get("nuget.config").expect("config rewritten"); + assert!( + out.contains( + " \n \n , ahead of the other sources: {out}" + ); + } + + #[test] + fn nuget_existing_mapping_socket_entry_lands_after_clear() { + let mut files = BTreeMap::new(); + files.insert( + "nuget.config".to_string(), + "\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n" + .to_string(), + ); + let r = rewrite_registry_redirect(&files, &[nuget_override()]); + let out = r.files.get("nuget.config").expect("config rewritten"); + assert!( + out.contains(" \n : {out}" + ); + assert!( + out.contains( + " \n \n " + ), + "socket mapping after the mapping's : {out}" + ); + assert!( + out.contains(" \n \n "), + "a in another section is not an anchor: {out}" + ); + } + fn berry_override(name: &str, version: &str, url: &str, checksum: &str) -> DepOverride { DepOverride { integrity: Integrity { From 375681d4f43ecd1757ae89bf7102a8de1a937503 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 23:08:20 +0000 Subject: [PATCH 2/4] Edit the NuGet config spelling NuGet reads NuGet reads the first of nuget.config, NuGet.config and NuGet.Config in a directory. Hosted mode only read nuget.config and vendored mode skipped NuGet.config, so on a case-sensitive filesystem a project whose config used another spelling got a new nuget.config that shadowed it. Its sources and mappings were ignored and packages from private feeds failed to restore. Both modes now edit the existing file in place and only create nuget.config when no spelling exists. Assisted-by: claude-code:claude-opus-5-5 --- CHANGELOG.md | 9 +++ .../src/commands/scan/hosted.rs | 4 ++ .../src/hosted_memory/redirect.rs | 2 +- .../src/hosted_memory/roots.rs | 10 ++- .../tests/in_process_get_hosted_ecosystems.rs | 66 +++++++++++++++++++ .../src/patch/redirect/mod.rs | 56 ++++++++++++++-- .../src/vendor/nuget_feed.rs | 41 +++++++++++- 7 files changed, 179 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23a54194..b85d8bf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -921,6 +921,15 @@ into the new version's section — see docs/releasing.md. NU1100 / NU1101 for the patched package. Both now land after the last ``. +- **nuget redirects and vendoring edit the config NuGet actually reads.** + NuGet reads the first of `nuget.config`, `NuGet.config` and + `NuGet.Config` in a directory. Hosted mode only knew `nuget.config` + and vendored mode missed `NuGet.config`, so on a case-sensitive + filesystem they created a fresh `nuget.config` that shadowed the + project's own file: its sources and mappings vanished and private + packages failed restore. Both modes now edit the existing spelling in + place. + - **`rollback` fetches a before-blob that only a store peer variant needs.** The before-blob gate now probes every pnpm and vlt store variant copy the rollback restores, so an online rollback no longer fails diff --git a/crates/socket-patch-cli/src/commands/scan/hosted.rs b/crates/socket-patch-cli/src/commands/scan/hosted.rs index 7cf3cc12..df813bf0 100644 --- a/crates/socket-patch-cli/src/commands/scan/hosted.rs +++ b/crates/socket-patch-cli/src/commands/scan/hosted.rs @@ -72,7 +72,11 @@ pub(crate) const REDIRECT_CANDIDATE_FILES: &[&str] = &[ // otherwise the `[registries.…]` block lands in a file cargo ignores. ".cargo/config", "composer.lock", + // Every spelling NuGet probes: the rewriter edits the one NuGet reads + // rather than shadowing it with a fresh `nuget.config`. "nuget.config", + "NuGet.config", + "NuGet.Config", "packages.lock.json", "Gemfile", "Gemfile.lock", diff --git a/crates/socket-patch-cli/src/hosted_memory/redirect.rs b/crates/socket-patch-cli/src/hosted_memory/redirect.rs index 7848425c..4a876341 100644 --- a/crates/socket-patch-cli/src/hosted_memory/redirect.rs +++ b/crates/socket-patch-cli/src/hosted_memory/redirect.rs @@ -178,7 +178,7 @@ fn file_ecosystem(rel: &str) -> Option<&'static str> { | "pyproject.toml" | "hatch.toml" => "pypi", "Cargo.toml" | "Cargo.lock" | "config.toml" | "config" => "cargo", "composer.lock" => "composer", - "nuget.config" | "packages.lock.json" => "nuget", + "nuget.config" | "NuGet.config" | "NuGet.Config" | "packages.lock.json" => "nuget", "Gemfile" | "Gemfile.lock" | "gems.rb" | "gems.locked" => "gem", "go.mod" | "go.sum" => "golang", "pom.xml" | "maven.config" | "checksums.sha256" => "maven", diff --git a/crates/socket-patch-cli/src/hosted_memory/roots.rs b/crates/socket-patch-cli/src/hosted_memory/roots.rs index 002cac15..bb5e15f8 100644 --- a/crates/socket-patch-cli/src/hosted_memory/roots.rs +++ b/crates/socket-patch-cli/src/hosted_memory/roots.rs @@ -48,7 +48,15 @@ pub(crate) const UNSUPPORTED_MARKERS: [(&str, &[&str]); 2] = [ "settings.gradle.kts", ], ), - ("nuget", &["packages.lock.json", "nuget.config"]), + ( + "nuget", + &[ + "packages.lock.json", + "nuget.config", + "NuGet.config", + "NuGet.Config", + ], + ), ]; /// Directory names whose subtrees never hold a project root: installed diff --git a/crates/socket-patch-cli/tests/in_process_get_hosted_ecosystems.rs b/crates/socket-patch-cli/tests/in_process_get_hosted_ecosystems.rs index 1a8c2a21..c563da33 100644 --- a/crates/socket-patch-cli/tests/in_process_get_hosted_ecosystems.rs +++ b/crates/socket-patch-cli/tests/in_process_get_hosted_ecosystems.rs @@ -651,6 +651,72 @@ async fn nuget_hosted_wires_source_mapping_and_lock_hash() { .unwrap(); } +/// NuGet reads `NuGet.config` when no `nuget.config` exists: the grant must +/// wire that file in place, not author a `nuget.config` that shadows it. +#[tokio::test] +#[serial] +async fn nuget_hosted_wires_mixed_case_config_in_place() { + const UUID: &str = "c4c4c4c4-c4c4-4c4c-8c4c-c4c4c4c4c4c4"; + const PURL: &str = "pkg:nuget/Newtonsoft.Json@13.0.3"; + let index_url = format!("http://patch.test/patch-registry/nuget/{TOKEN}/{UUID}/index.json"); + let url = format!( + "http://patch.test/patch-registry/nuget/{TOKEN}/{UUID}/flat/newtonsoft.json/13.0.3/newtonsoft.json.13.0.3.nupkg" + ); + + let server = MockServer::start().await; + mock_view(&server, UUID, PURL).await; + mock_reference( + &server, + UUID, + PURL, + &url, + serde_json::json!({ "sha512": "sha512-NUGETPATCHED==" }), + serde_json::json!({ + "kind": "nuget-v3", + "indexUrl": index_url, + "identifiers": { + "name": "Newtonsoft.Json", + "version": "13.0.3", + "nugetIdLower": "newtonsoft.json", + "nugetVersionNorm": "13.0.3", + } + }), + ) + .await; + + let tmp = tempfile::tempdir().unwrap(); + std::fs::write( + tmp.path().join("NuGet.config"), + r#" + + + + + +"#, + ) + .unwrap(); + let case_sensitive = !tmp.path().join("nuget.config").exists(); + + let code = + socket_patch_cli::commands::get::run(get_hosted_args(UUID, tmp.path(), server.uri())).await; + assert_eq!(code, 0, "get --mode hosted (nuget) should succeed"); + + let config = std::fs::read_to_string(tmp.path().join("NuGet.config")).unwrap(); + assert!( + config.contains(&format!( + r#""# + )) && config.contains(r#" String { "\n\n \n \n \n\n".to_string() } @@ -5343,13 +5348,19 @@ fn rewrite_nuget( if nuget.is_empty() { return; } + // NuGet reads the first of these spellings present in a directory; a + // fresh `nuget.config` beside a `NuGet.config` would shadow it. + let config_path = NUGET_CONFIG_FILE_NAMES + .into_iter() + .find(|name| files.contains_key(*name)) + .unwrap_or(NUGET_CONFIG_FILE_NAMES[0]); let mut config = files - .get("nuget.config") + .get(config_path) .cloned() .unwrap_or_else(default_nuget_config); // A config this run authors from scratch records its source edits as // `added` — the spelling every other rewriter uses for a created file. - let source_action = if files.contains_key("nuget.config") { + let source_action = if files.contains_key(config_path) { "rewritten" } else { "added" @@ -5428,7 +5439,7 @@ fn rewrite_nuget( config = updated; config_changed = true; result.edits.push(FileEdit { - path: "nuget.config".into(), + path: config_path.into(), kind: "redirect_nuget_source".into(), action: source_action.into(), key: Some(reg.clone()), @@ -5491,7 +5502,7 @@ fn rewrite_nuget( } if config_changed { - result.files.insert("nuget.config".into(), config); + result.files.insert(config_path.into(), config); } if lock_changed { if let Some(lock_val) = lock { @@ -8337,6 +8348,43 @@ mod tests { ); } + /// NuGet reads the first of `nuget.config`, `NuGet.config`, + /// `NuGet.Config` present; authoring `nuget.config` beside another + /// spelling would shadow the project's sources. + #[test] + fn nuget_config_spelling_is_rewritten_in_place() { + let config = "\n\n \n \n \n\n"; + for (present, expected) in [ + (&["NuGet.config"][..], "NuGet.config"), + (&["NuGet.Config"][..], "NuGet.Config"), + (&["NuGet.Config", "NuGet.config"][..], "NuGet.config"), + (&["NuGet.Config", "nuget.config"][..], "nuget.config"), + ] { + let files: BTreeMap = present + .iter() + .map(|name| (name.to_string(), config.to_string())) + .collect(); + let r = rewrite_registry_redirect(&files, &[nuget_override()]); + assert_eq!( + r.files.keys().collect::>(), + vec![expected], + "{present:?}" + ); + assert!( + r.files[expected].contains("key=\"corp-feed\""), + "{present:?}: {}", + r.files[expected] + ); + let source_edit = r + .edits + .iter() + .find(|e| e.kind == "redirect_nuget_source") + .expect("source edit recorded"); + assert_eq!(source_edit.path, expected, "{present:?}"); + assert_eq!(source_edit.action, "rewritten", "{present:?}"); + } + } + fn berry_override(name: &str, version: &str, url: &str, checksum: &str) -> DepOverride { DepOverride { integrity: Integrity { diff --git a/crates/socket-patch-core/src/vendor/nuget_feed.rs b/crates/socket-patch-core/src/vendor/nuget_feed.rs index 61ed8c6d..37c3acae 100644 --- a/crates/socket-patch-core/src/vendor/nuget_feed.rs +++ b/crates/socket-patch-core/src/vendor/nuget_feed.rs @@ -1140,10 +1140,10 @@ struct ConfigEdit { mapping_fragment: String, } -/// Resolve the existing `nuget.config` (prefer lowercase `nuget.config`, then -/// `NuGet.Config`), or `None` when the project has none. +/// Resolve the existing config in NuGet's own probe order, or `None` when the +/// project has none. async fn existing_config_path(project_root: &Path) -> Option { - for name in ["nuget.config", "NuGet.Config"] { + for name in crate::patch::redirect::NUGET_CONFIG_FILE_NAMES { let p = project_root.join(name); // Answers from a group-committed run's capture: a config an earlier // package of this run created is not on disk yet. @@ -2535,6 +2535,41 @@ mod tests { ); } + /// NuGet reads `NuGet.config` when no `nuget.config` exists; creating + /// one beside it would shadow the project's own sources. + #[tokio::test] + async fn wiring_edits_mixed_case_nuget_config_in_place() { + let orig_cfg = "\n\ + \n\ + \x20 \n\ + \x20 \n\ + \x20 \n\ + \n"; + let (dir, blobs, installed, record) = fixture(true, None).await; + let root = dir.path(); + tokio::fs::write(root.join("NuGet.config"), orig_cfg) + .await + .unwrap(); + let case_sensitive = !root.join("nuget.config").exists(); + + let (_r, entry, _w) = + unwrap_done(run_vendor(root, &blobs, &installed, &record, false).await); + let entry = entry.unwrap(); + assert_eq!(entry.wiring[0].action, WiringAction::Rewritten); + let wired = tokio::fs::read_to_string(root.join("NuGet.config")) + .await + .unwrap(); + assert!(wired.contains(&format!("socket-patch-{UUID}")), "{wired}"); + assert!(wired.contains("key=\"corp\""), "{wired}"); + if case_sensitive { + assert_eq!(entry.wiring[0].file, "NuGet.config"); + assert!( + !root.join("nuget.config").exists(), + "no shadowing nuget.config created" + ); + } + } + #[tokio::test] async fn revert_excises_only_our_source_preserving_sibling() { // A pre-existing config. Vendor wires OUR source + mapping. Then a From 965995e6ea14db569c1713976ed2c02b38cc8422 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 11:03:27 +0000 Subject: [PATCH 3/4] fix(core/redirect): match NuGet section tags tolerantly for inserts The after- insert found section bounds with literal `` / `` and the mapping open tag with a literal ``. A close tag with whitespace before `>` sent the insert back ahead of the ``, which drops it; a spaced mapping open tag authored a duplicate section. A commented-out `` also became the anchor, splicing the Socket source inside the comment. Match tags with the same whitespace/attribute tolerance as the rest of the rewriter and skip comments. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NPYQfCTLY7F4eQTa8hjinL --- .../src/patch/redirect/mod.rs | 99 +++++++++++++++++-- 1 file changed, 90 insertions(+), 9 deletions(-) diff --git a/crates/socket-patch-core/src/patch/redirect/mod.rs b/crates/socket-patch-core/src/patch/redirect/mod.rs index 173f7d7c..edf0acb7 100644 --- a/crates/socket-patch-core/src/patch/redirect/mod.rs +++ b/crates/socket-patch-core/src/patch/redirect/mod.rs @@ -5157,7 +5157,10 @@ fn add_nuget_source(config: &str, reg: &str, index_url: &str, pkg_id: &str) -> O // socket-only and every other package would fail. Seed the implicit default // nuget.org source so the catch-all has a real target (unless the config // already has one). Only relevant when we are about to CREATE the mapping. - let creating_mapping = !out.contains(""); + // The open tag may carry whitespace or attributes (`` + // is valid XML); a literal probe reads it as absent and authors a + // DUPLICATE section. + let creating_mapping = nuget_mapping_open_end(&out).is_none(); // "Already has one" is decided by the parsed keys ALONE: // a whole-file "nuget.org" probe is satisfied by text that defines no // source (a defaultPushSource URL, a entry, a @@ -5180,8 +5183,8 @@ fn add_nuget_source(config: &str, reg: &str, index_url: &str, pkg_id: &str) -> O // already covered. // After any `` in the section: NuGet drops every mapping // read before one, leaving the patched id routed nowhere. - let open_end = out.find("")? + "".len(); - let at = nuget_after_last_clear(&out, open_end, ""); + let open_end = nuget_mapping_open_end(&out)?; + let at = nuget_after_last_clear(&out, open_end, "packageSourceMapping"); out = format!("{}\n{socket_mapping}{}", &out[..at], &out[at..]); } else { // Creating the mapping from scratch. Once ANY @@ -5256,7 +5259,7 @@ fn insert_nuget_source(config: &str, key: &str, url: &str) -> Option { { // After any ``: NuGet drops every source read before one, // so the mapping would point at an undefined source (NU1100). - let end = nuget_after_last_clear(config, m.end(), ""); + let end = nuget_after_last_clear(config, m.end(), "packageSources"); Some(format!( "{}\n{source_line}{}", &config[..end], @@ -5278,16 +5281,42 @@ fn insert_nuget_source(config: &str, key: &str, url: &str) -> Option { } } -/// The offset just past the last `` between `from` and the next -/// `close` tag, else `from`. -fn nuget_after_last_clear(config: &str, from: usize, close: &str) -> usize { +/// The offset just past the `` open tag (any whitespace +/// or attributes), or `None` when the config has no open/close section — a +/// self-closing `` holds no children to append to. +fn nuget_mapping_open_end(config: &str) -> Option { + static OPEN_RE: LazyLock = LazyLock::new(|| { + Regex::new(r"]*)?>") + .expect("static packageSourceMapping open-tag regex is valid") + }); + OPEN_RE + .find(config) + .filter(|m| !m.as_str().ends_with("/>")) + .map(|m| m.end()) +} + +/// The offset just past the last `` between `from` and the `section` +/// element's close tag (any whitespace before `>`), else `from`. Comments are +/// skipped: a commented-out `` clears nothing, and anchoring on it +/// would splice the new entry INSIDE the comment. +fn nuget_after_last_clear(config: &str, from: usize, section: &str) -> usize { static CLEAR_RE: LazyLock = LazyLock::new(|| Regex::new(r"").expect("static clear-tag regex is valid")); - let Some(len) = config[from..].find(close) else { + static COMMENT_RE: LazyLock = + LazyLock::new(|| Regex::new(r"(?s)").expect("static comment regex is valid")); + // Blank comment bytes in place so offsets still index `config`. + let mut masked = config[from..].as_bytes().to_vec(); + for m in COMMENT_RE.find_iter(&config[from..]) { + masked[m.range()].fill(b' '); + } + let masked = String::from_utf8(masked).expect("only whole comments are blanked"); + let close_re = + Regex::new(&format!(r"")).expect("section close-tag regex is valid"); + let Some(close) = close_re.find(&masked) else { return from; }; CLEAR_RE - .find_iter(&config[from..from + len]) + .find_iter(&masked[..close.start()]) .last() .map_or(from, |m| from + m.end()) } @@ -8348,6 +8377,58 @@ mod tests { ); } + /// Close tags with whitespace before `>` are valid XML: a literal probe + /// misses the section, falls back to the open tag and lands the Socket + /// entries ahead of the `` that drops them. + #[test] + fn nuget_after_clear_tolerates_spaced_tags() { + let mut files = BTreeMap::new(); + files.insert( + "nuget.config".to_string(), + "\n\n \n \n \n \n \n \n \n \n \n \n\n" + .to_string(), + ); + let r = rewrite_registry_redirect(&files, &[nuget_override()]); + let out = r.files.get("nuget.config").expect("config rewritten"); + assert!( + out.contains(" \n \n : {out}" + ); + assert!( + out.contains( + " \n \n " + ), + "socket mapping after the mapping's : {out}" + ); + assert_eq!( + out.matches("` clears nothing; anchoring on it would + /// splice the Socket source inside the comment. + #[test] + fn nuget_commented_clear_is_not_an_anchor() { + let mut files = BTreeMap::new(); + files.insert( + "nuget.config".to_string(), + "\n\n \n \n \n \n \n\n" + .to_string(), + ); + let r = rewrite_registry_redirect(&files, &[nuget_override()]); + let out = r.files.get("nuget.config").expect("config rewritten"); + assert!( + out.contains(" \n : {out}" + ); + assert!( + out.contains(" \n"), + "comment left intact: {out}" + ); + } + /// NuGet reads the first of `nuget.config`, `NuGet.config`, /// `NuGet.Config` present; authoring `nuget.config` beside another /// spelling would shadow the project's sources. From 02ee0a6b265cbb19a42d98b1a30e258d8c7be318 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 11:11:21 +0000 Subject: [PATCH 4/4] fix(core/redirect): slice bytes, not str, when masking comments Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NPYQfCTLY7F4eQTa8hjinL --- crates/socket-patch-core/src/patch/redirect/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/socket-patch-core/src/patch/redirect/mod.rs b/crates/socket-patch-core/src/patch/redirect/mod.rs index edf0acb7..d597fc3c 100644 --- a/crates/socket-patch-core/src/patch/redirect/mod.rs +++ b/crates/socket-patch-core/src/patch/redirect/mod.rs @@ -5305,7 +5305,7 @@ fn nuget_after_last_clear(config: &str, from: usize, section: &str) -> usize { static COMMENT_RE: LazyLock = LazyLock::new(|| Regex::new(r"(?s)").expect("static comment regex is valid")); // Blank comment bytes in place so offsets still index `config`. - let mut masked = config[from..].as_bytes().to_vec(); + let mut masked = config.as_bytes()[from..].to_vec(); for m in COMMENT_RE.find_iter(&config[from..]) { masked[m.range()].fill(b' '); }