Skip to content

Commit 950b9e6

Browse files
committed
Refuse an unreadable socket-patch.sbt before any takeover
92a44fd dropped socket-patch.sbt from the run-wide non-UTF-8 check so the sbt refusal could report its own code. That also hid it from the pre-takeover check, so a mixed run (a vendored PyPI purl plus sbt) could revert vendored wiring and only then be refused, leaving those packages unpatched in both modes (Bugbot). The file stays in the early check, which now refuses it with redirect_sbt_owned_file_unreadable, before any revert. Refs #721 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YED4tY7Yytk79MTPzLnSfA
1 parent 92a44fd commit 950b9e6

2 files changed

Lines changed: 41 additions & 8 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2675,7 +2675,7 @@ fn created_settings_over_existing(
26752675
}
26762676

26772677
/// [`created_settings_over_existing`]'s code for `socket-patch.sbt`.
2678-
const SBT_OWNED_FILE_UNREADABLE: &str = "redirect_sbt_owned_file_unreadable";
2678+
use socket_patch_core::hosted::engine::SBT_OWNED_FILE_UNREADABLE;
26792679

26802680
#[cfg(test)]
26812681
mod tests {

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

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,22 @@ fn unreadable_refusal(rel: &str) -> Refusal {
158158
}
159159
}
160160

161+
/// The refusal for an unreadable `socket-patch.sbt`, the file the sbt
162+
/// planner owns and would otherwise create over the user's bytes.
163+
pub const SBT_OWNED_FILE_UNREADABLE: &str = "redirect_sbt_owned_file_unreadable";
164+
161165
fn undecodable_refusal(rel: &str) -> Refusal {
166+
// Keeps the sbt planner's own refusal code, and still refuses here,
167+
// before any vendored->hosted takeover revert.
168+
if rel == crate::formats::sbt::owned_file::HOSTED_FILE {
169+
return Refusal {
170+
code: SBT_OWNED_FILE_UNREADABLE.to_string(),
171+
message: format!(
172+
"{rel} is not UTF-8 text, so the hosted sbt wiring would replace it; \
173+
re-save it as UTF-8 and re-run; nothing was written"
174+
),
175+
};
176+
}
162177
Refusal {
163178
code: UNREADABLE_REFUSAL.to_string(),
164179
message: format!(
@@ -627,13 +642,6 @@ pub async fn read_candidate_files(
627642
out.undecodable_reads
628643
.retain(|rel| !is_gradle_owned_file(rel));
629644
}
630-
// `socket-patch.sbt` the sbt planner takes for absent and would create:
631-
// on disk the scan refuses that write with its own
632-
// `redirect_sbt_owned_file_unreadable`, so leave it to that refusal.
633-
if !matches!(view, ProjectView::Memory(_)) {
634-
out.undecodable_reads
635-
.retain(|rel| rel != crate::formats::sbt::owned_file::HOSTED_FILE);
636-
}
637645
// An sbt build's resolution evidence rides a synthetic key (see
638646
// `patch::redirect::sbt::SBT_RESOLUTION_KEY`).
639647
if candidates.iter().any(|c| c.dep.ecosystem == "maven")
@@ -2922,6 +2930,31 @@ mod tests {
29222930
}
29232931
}
29242932

2933+
/// #721 review: an unreadable `socket-patch.sbt` is refused by the
2934+
/// run-wide check itself, which also runs before any vendored->hosted
2935+
/// takeover revert, and keeps the sbt planner's own refusal code.
2936+
#[tokio::test]
2937+
async fn an_unreadable_sbt_owned_file_refuses_early_with_the_sbt_code() {
2938+
let latin1: &[u8] = b"// Auteur: Andr\xe9\n";
2939+
let tmp = tempfile::tempdir().unwrap();
2940+
std::fs::write(tmp.path().join("socket-patch.sbt"), latin1).unwrap();
2941+
let candidates = vec![gradle_candidate()];
2942+
let read = read_candidate_files(
2943+
&ProjectView::Disk(tmp.path()),
2944+
&BTreeSet::new(),
2945+
&candidates,
2946+
)
2947+
.await;
2948+
assert_eq!(read.undecodable_reads, vec!["socket-patch.sbt"]);
2949+
let refusal = undecodable_guard(&read.undecodable_reads, &candidates).expect("refused");
2950+
assert_eq!(refusal.code, SBT_OWNED_FILE_UNREADABLE);
2951+
assert!(
2952+
refusal.message.contains("socket-patch.sbt"),
2953+
"{}",
2954+
refusal.message
2955+
);
2956+
}
2957+
29252958
/// A refused Gradle build is never confirmed by a snippet pasted into a
29262959
/// build script, though it names the suffixed version and the index url.
29272960
#[tokio::test]

0 commit comments

Comments
 (0)