From 7b57dc33bb896b6b621237c72ac4651e044d2061 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 6 Jul 2026 09:52:02 -0400 Subject: [PATCH] Reject overlapping EOF patch chunks --- codex-rs/apply-patch/src/seek_sequence.rs | 2 +- codex-rs/apply-patch/tests/suite/tool.rs | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/codex-rs/apply-patch/src/seek_sequence.rs b/codex-rs/apply-patch/src/seek_sequence.rs index 3555963120..5e3ab000e3 100644 --- a/codex-rs/apply-patch/src/seek_sequence.rs +++ b/codex-rs/apply-patch/src/seek_sequence.rs @@ -27,7 +27,7 @@ pub(crate) fn seek_sequence( return None; } let search_start = if eof && lines.len() >= pattern.len() { - lines.len() - pattern.len() + (lines.len() - pattern.len()).max(start) } else { start }; diff --git a/codex-rs/apply-patch/tests/suite/tool.rs b/codex-rs/apply-patch/tests/suite/tool.rs index 9ec880931d..30661624f4 100644 --- a/codex-rs/apply-patch/tests/suite/tool.rs +++ b/codex-rs/apply-patch/tests/suite/tool.rs @@ -84,6 +84,26 @@ fn test_apply_patch_cli_applies_multiple_chunks() -> anyhow::Result<()> { Ok(()) } +#[test] +fn test_apply_patch_cli_rejects_overlapping_end_of_file_chunks() -> anyhow::Result<()> { + let tmp = tempdir()?; + let target_path = tmp.path().join("overlapping.txt"); + let expected_target_path = resolved_under(tmp.path(), "overlapping.txt")?; + fs::write(&target_path, "one\n")?; + + let patch = "*** Begin Patch\n*** Update File: overlapping.txt\n@@\n-one\n+first\n@@\n-one\n+second\n*** End of File\n*** End Patch"; + + run_apply_patch_in_dir(tmp.path(), patch)? + .failure() + .stderr(format!( + "Failed to find expected lines in {}:\none\n", + expected_target_path.display() + )); + + assert_eq!(fs::read_to_string(target_path)?, "one\n"); + Ok(()) +} + #[test] fn test_apply_patch_cli_preserves_crlf_from_target_file() -> anyhow::Result<()> { let patch = "*** Begin Patch\n*** Update File: crlf.txt\n@@\n-one\n+uno\n@@\n two\n+\n+between\n three\n*** End Patch";