diff --git a/crates/socket-patch-core/src/api/blob_fetcher.rs b/crates/socket-patch-core/src/api/blob_fetcher.rs index 8496e9e..7309070 100644 --- a/crates/socket-patch-core/src/api/blob_fetcher.rs +++ b/crates/socket-patch-core/src/api/blob_fetcher.rs @@ -253,6 +253,21 @@ async fn download_hashes( match client.fetch_blob(hash).await { Ok(Some(data)) => { + // Verify content hash matches expected hash before writing + let actual_hash = crate::hash::git_sha256::compute_git_sha256_from_bytes(&data); + if actual_hash != *hash { + results.push(BlobFetchResult { + hash: hash.clone(), + success: false, + error: Some(format!( + "Content hash mismatch: expected {}, got {}", + hash, actual_hash + )), + }); + failed += 1; + continue; + } + let blob_path: PathBuf = blobs_path.join(hash); match tokio::fs::write(&blob_path, &data).await { Ok(()) => { diff --git a/crates/socket-patch-core/src/manifest/operations.rs b/crates/socket-patch-core/src/manifest/operations.rs index cec161f..30fae4a 100644 --- a/crates/socket-patch-core/src/manifest/operations.rs +++ b/crates/socket-patch-core/src/manifest/operations.rs @@ -104,24 +104,31 @@ pub fn validate_manifest(value: &serde_json::Value) -> Result) -> Result, std::io::Error> { let path = path.as_ref(); let content = match tokio::fs::read_to_string(path).await { Ok(c) => c, Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None), - Err(_) => return Ok(None), + Err(e) => return Err(e), // FIX: propagate actual I/O error }; let parsed: serde_json::Value = match serde_json::from_str(&content) { Ok(v) => v, - Err(_) => return Ok(None), + Err(e) => return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!("Failed to parse manifest JSON: {}", e), + )), }; match validate_manifest(&parsed) { Ok(manifest) => Ok(Some(manifest)), - Err(_) => Ok(None), + Err(e) => Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + e, + )), } } diff --git a/crates/socket-patch-core/src/patch/apply.rs b/crates/socket-patch-core/src/patch/apply.rs index a15c3b3..2fcb28d 100644 --- a/crates/socket-patch-core/src/patch/apply.rs +++ b/crates/socket-patch-core/src/patch/apply.rs @@ -254,13 +254,33 @@ pub async fn apply_package_patch( result.files_verified.push(verify_result); } - // Check if all files are already patched (or skipped due to NotFound with force) - let all_patched = result + // Check if all files are already patched + let all_already_patched = result + .files_verified + .iter() + .all(|v| v.status == VerifyStatus::AlreadyPatched); + + if all_already_patched { + result.success = true; + return result; + } + + // Check if all files are either already patched or not found (force mode skip) + let all_done_or_skipped = result .files_verified .iter() .all(|v| v.status == VerifyStatus::AlreadyPatched || v.status == VerifyStatus::NotFound); - if all_patched { + + if all_done_or_skipped { + // Some or all files were not found but skipped via --force + let not_found_count = result.files_verified.iter() + .filter(|v| v.status == VerifyStatus::NotFound) + .count(); result.success = true; + result.error = Some(format!( + "All patch files were skipped: {} not found on disk (--force)", + not_found_count + )); return result; }