From 7f932b8552c22355902829be1c5615a867d1bd45 Mon Sep 17 00:00:00 2001 From: Prince Jain Date: Thu, 22 Oct 2026 23:15:26 +0530 Subject: [PATCH] fix(nodejs): reject listNamespaces limit above i32::MAX instead of wrapping (#4584) ## Summary Fixes #4583. `Connection::list_namespaces` narrowed `Option` to `i32` with an unchecked `as` cast, so a JavaScript `limit` between 2^31 and 2^32 - 1 (e.g. `3000000000`) wrapped negative before reaching `ListNamespacesRequest`. The server received a negative page size instead of a clear client-side error. Replaced the cast with a `checked_limit` helper using `i32::try_from`, returning `napi::Error::from_reason` on overflow, matching the existing `try_from` validation used for `limit` elsewhere in the same file. ## Tests Unit tests cover the wrap case (`u32` values above `i32::MAX` reject), the boundary (`i32::MAX` passes), and `None`/zero passthrough. `cargo test -p lancedb-nodejs` passes 8/8 and `pnpm build` compiles the binding and TypeScript definitions cleanly. --- nodejs/src/connection.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/nodejs/src/connection.rs b/nodejs/src/connection.rs index 58ec9859a..67c4706e4 100644 --- a/nodejs/src/connection.rs +++ b/nodejs/src/connection.rs @@ -673,7 +673,7 @@ impl Connection { let req = ListNamespacesRequest { id: namespace_path, page_token, - limit: limit.map(|l| l as i32), + limit: checked_limit(limit)?, ..Default::default() }; let resp = self @@ -787,3 +787,33 @@ impl Connection { .default_error() } } + +fn checked_limit(limit: Option) -> napi::Result> { + limit + .map(i32::try_from) + .transpose() + .map_err(|_| napi::Error::from_reason("limit must not exceed 2147483647")) +} + +#[cfg(test)] +mod tests { + use super::checked_limit; + + #[test] + fn checked_limit_rejects_values_above_i32_max() { + // u32 values above i32::MAX wrapped negative through `as i32`, + // sending a negative page size to the server. + assert!(checked_limit(Some(3_000_000_000)).is_err()); + assert!(checked_limit(Some(u32::MAX)).is_err()); + } + + #[test] + fn checked_limit_passes_boundary_and_missing_values() { + assert_eq!( + checked_limit(Some(i32::MAX as u32)).unwrap(), + Some(i32::MAX) + ); + assert_eq!(checked_limit(Some(0)).unwrap(), Some(0)); + assert_eq!(checked_limit(None).unwrap(), None); + } +}