From ce24728443a94cd377eddd8dbf943380c7a790e5 Mon Sep 17 00:00:00 2001 From: jewoos2921 <40465417+jewoos2921@users.noreply.github.com> Date: Wed, 29 Jul 2026 13:40:05 +0900 Subject: [PATCH] fix(robustness): replace risky production unwrap() with typed error handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove unwrap() calls from production paths where runtime panics could be triggered by external input or platform-specific conditions: - cas_publish.rs: pack_path.to_str().unwrap() → ok_or_else(CasError)? Non-UTF-8 temp paths (e.g. Windows usernames with non-ASCII chars) would panic the relay during git index-pack. - validation.rs: 9x try_into().unwrap() in PNG/WebP/MP4/GIF metadata validation → expect() with documented invariant. While logically safe after bounds checks, expect() makes the invariant explicit and produces a clear diagnostic if the assumption ever breaks. - agents.rs: events.next().unwrap() → expect() with invariant comment. Already guarded by an is_empty() check above; made the safety explicit. Note: side_effects.rs role_str.parse().unwrap() and actor_member.unwrap() were also addressed but main already refactored this code to remove them. --- crates/buzz-cli/src/commands/agents.rs | 6 ++++- crates/buzz-media/src/validation.rs | 24 ++++++++++++-------- crates/buzz-relay/src/api/git/cas_publish.rs | 8 ++++++- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/crates/buzz-cli/src/commands/agents.rs b/crates/buzz-cli/src/commands/agents.rs index 58564a45c2..2bb91e695f 100644 --- a/crates/buzz-cli/src/commands/agents.rs +++ b/crates/buzz-cli/src/commands/agents.rs @@ -296,7 +296,11 @@ pub(crate) async fn fetch_archived_snapshot(client: &BuzzClient) -> Result Result<(), MediaError> { if i + 12 > bytes.len() { return Err(MediaError::InvalidImage); } - let len = u32::from_be_bytes(bytes[i..i + 4].try_into().unwrap()) as usize; - let kind: [u8; 4] = bytes[i + 4..i + 8].try_into().unwrap(); + // Safe: the bounds check above guarantees at least 12 bytes from `i`. + let len = u32::from_be_bytes(bytes[i..i + 4].try_into().expect("bounds checked")) as usize; + let kind: [u8; 4] = bytes[i + 4..i + 8].try_into().expect("bounds checked"); let end = i .checked_add(12) .and_then(|v| v.checked_add(len)) @@ -670,8 +671,10 @@ fn validate_webp_metadata_free(bytes: &[u8]) -> Result<(), MediaError> { if i + 8 > payload.len() { return Err(MediaError::InvalidImage); } - let kind: [u8; 4] = payload[i..i + 4].try_into().unwrap(); - let len = u32::from_le_bytes(payload[i + 4..i + 8].try_into().unwrap()) as usize; + // Safe: the bounds check above guarantees at least 8 bytes from `i`. + let kind: [u8; 4] = payload[i..i + 4].try_into().expect("bounds checked"); + let len = u32::from_le_bytes(payload[i + 4..i + 8].try_into().expect("bounds checked")) + as usize; let padded = len.checked_add(len & 1).ok_or(MediaError::InvalidImage)?; i = i .checked_add(8) @@ -694,7 +697,7 @@ fn validate_webp_metadata_free(bytes: &[u8]) -> Result<(), MediaError> { if bytes.len() < 12 || &bytes[..4] != b"RIFF" || &bytes[8..12] != b"WEBP" { return Err(MediaError::InvalidImage); } - let declared = u32::from_le_bytes(bytes[4..8].try_into().unwrap()) as usize; + let declared = u32::from_le_bytes(bytes[4..8].try_into().expect("len >= 12 checked")) as usize; if declared.checked_add(8) != Some(bytes.len()) { return Err(MediaError::MetadataForbidden); } @@ -703,8 +706,10 @@ fn validate_webp_metadata_free(bytes: &[u8]) -> Result<(), MediaError> { if i + 8 > bytes.len() { return Err(MediaError::InvalidImage); } - let kind: [u8; 4] = bytes[i..i + 4].try_into().unwrap(); - let len = u32::from_le_bytes(bytes[i + 4..i + 8].try_into().unwrap()) as usize; + // Safe: the bounds check above guarantees at least 8 bytes from `i`. + let kind: [u8; 4] = bytes[i..i + 4].try_into().expect("bounds checked"); + let len = + u32::from_le_bytes(bytes[i + 4..i + 8].try_into().expect("bounds checked")) as usize; let payload_start = i + 8; let padded = len.checked_add(len & 1).ok_or(MediaError::InvalidImage)?; i = payload_start @@ -882,8 +887,9 @@ fn validate_mp4_metadata_free(path: &Path) -> Result<(), MediaError> { let mut h = [0u8; 8]; file.read_exact(&mut h) .map_err(|_| MediaError::InvalidVideo)?; - let compact = u32::from_be_bytes(h[..4].try_into().unwrap()) as u64; - let kind: [u8; 4] = h[4..8].try_into().unwrap(); + // `h` is a fixed [u8; 8]; these slices are always exactly 4 bytes. + let compact = u32::from_be_bytes(h[..4].try_into().expect("fixed 8-byte array")) as u64; + let kind: [u8; 4] = h[4..8].try_into().expect("fixed 8-byte array"); let (size, header) = if compact == 1 { let mut ext = [0u8; 8]; file.read_exact(&mut ext) diff --git a/crates/buzz-relay/src/api/git/cas_publish.rs b/crates/buzz-relay/src/api/git/cas_publish.rs index 635dcf2a67..3347d345a7 100644 --- a/crates/buzz-relay/src/api/git/cas_publish.rs +++ b/crates/buzz-relay/src/api/git/cas_publish.rs @@ -406,8 +406,14 @@ async fn write_idx_sidecar( .await .map_err(|e| CasError::PackCapture(format!("write idx input pack {pack_digest}: {e}")))?; + let pack_path_str = pack_path.to_str().ok_or_else(|| { + CasError::PackCapture(format!( + "idx pack path is not valid UTF-8: {}", + pack_path.display() + )) + })?; let mut cmd = Command::new("git"); - cmd.args(["index-pack", pack_path.to_str().unwrap()]) + cmd.args(["index-pack", pack_path_str]) .current_dir(tempdir.path()); super::transport::harden_git_env(&mut cmd); let out = cmd