Skip to content

Commit daff491

Browse files
chaliyclaude
andauthored
fix(network): use try_from instead of truncating u64-to-usize cast (#472)
## Summary - `content_length as usize` and limit casts silently truncate on 32-bit platforms - Changed to `usize::try_from().unwrap_or(usize::MAX)` for safe saturation ## Test plan - [x] Unit test: `test_u64_to_usize_no_truncation` Closes #430 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7f003ab commit daff491

File tree

2 files changed

+19
-10
lines changed

2 files changed

+19
-10
lines changed

crates/bashkit-python/src/lib.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -219,10 +219,10 @@ impl PyBash {
219219

220220
let mut limits = ExecutionLimits::new();
221221
if let Some(mc) = max_commands {
222-
limits = limits.max_commands(mc as usize);
222+
limits = limits.max_commands(usize::try_from(mc).unwrap_or(usize::MAX));
223223
}
224224
if let Some(mli) = max_loop_iterations {
225-
limits = limits.max_loop_iterations(mli as usize);
225+
limits = limits.max_loop_iterations(usize::try_from(mli).unwrap_or(usize::MAX));
226226
}
227227
builder = builder.limits(limits);
228228

@@ -313,10 +313,10 @@ impl PyBash {
313313
}
314314
let mut limits = ExecutionLimits::new();
315315
if let Some(mc) = max_commands {
316-
limits = limits.max_commands(mc as usize);
316+
limits = limits.max_commands(usize::try_from(mc).unwrap_or(usize::MAX));
317317
}
318318
if let Some(mli) = max_loop_iterations {
319-
limits = limits.max_loop_iterations(mli as usize);
319+
limits = limits.max_loop_iterations(usize::try_from(mli).unwrap_or(usize::MAX));
320320
}
321321
builder = builder.limits(limits);
322322
*bash = builder.build();
@@ -396,10 +396,10 @@ impl BashTool {
396396

397397
let mut limits = ExecutionLimits::new();
398398
if let Some(mc) = max_commands {
399-
limits = limits.max_commands(mc as usize);
399+
limits = limits.max_commands(usize::try_from(mc).unwrap_or(usize::MAX));
400400
}
401401
if let Some(mli) = max_loop_iterations {
402-
limits = limits.max_loop_iterations(mli as usize);
402+
limits = limits.max_loop_iterations(usize::try_from(mli).unwrap_or(usize::MAX));
403403
}
404404
builder = builder.limits(limits);
405405

@@ -622,10 +622,10 @@ impl ScriptedTool {
622622
if self.max_commands.is_some() || self.max_loop_iterations.is_some() {
623623
let mut limits = ExecutionLimits::new();
624624
if let Some(mc) = self.max_commands {
625-
limits = limits.max_commands(mc as usize);
625+
limits = limits.max_commands(usize::try_from(mc).unwrap_or(usize::MAX));
626626
}
627627
if let Some(mli) = self.max_loop_iterations {
628-
limits = limits.max_loop_iterations(mli as usize);
628+
limits = limits.max_loop_iterations(usize::try_from(mli).unwrap_or(usize::MAX));
629629
}
630630
builder = builder.limits(limits);
631631
}

crates/bashkit/src/network/client.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ impl HttpClient {
233233

234234
// Check Content-Length header to fail fast on large responses
235235
if let Some(content_length) = response.content_length() {
236-
if content_length as usize > self.max_response_bytes {
236+
if usize::try_from(content_length).unwrap_or(usize::MAX) > self.max_response_bytes {
237237
return Err(Error::Network(format!(
238238
"response too large: {} bytes (max: {} bytes)",
239239
content_length, self.max_response_bytes
@@ -416,7 +416,7 @@ impl HttpClient {
416416

417417
// Check Content-Length header to fail fast on large responses
418418
if let Some(content_length) = response.content_length() {
419-
if content_length as usize > self.max_response_bytes {
419+
if usize::try_from(content_length).unwrap_or(usize::MAX) > self.max_response_bytes {
420420
return Err(Error::Network(format!(
421421
"response too large: {} bytes (max: {} bytes)",
422422
content_length, self.max_response_bytes
@@ -528,6 +528,15 @@ mod tests {
528528
assert!(result.unwrap_err().to_string().contains("access denied"));
529529
}
530530

531+
#[test]
532+
fn test_u64_to_usize_no_truncation() {
533+
// On 64-bit: fits fine. On 32-bit: saturates to usize::MAX rather than truncating.
534+
let large: u64 = 5_368_709_120; // 5GB
535+
let result = usize::try_from(large).unwrap_or(usize::MAX);
536+
// Should never silently become a smaller value
537+
assert!(result >= large.min(usize::MAX as u64) as usize);
538+
}
539+
531540
// Note: Integration tests that actually make network requests
532541
// should be in a separate test file and marked with #[ignore]
533542
// to avoid network dependencies in unit tests.

0 commit comments

Comments
 (0)