From 9ddf6c06beb8e7dd2d12e36dd449ccf269578c3e Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Sun, 19 Apr 2026 15:17:40 -0400 Subject: [PATCH] security(server): clamp pagination offset before iterator traversal CodeQL's rust/uncontrolled-allocation-size alert flagged `paginate_items` and the models list handler because `PaginationParams.offset: u32` was cast to `usize` without an upper bound and handed to `Iterator::skip`. In practice the underlying stores are bounded and `skip` on a Vec iterator is O(1), so the existing callers couldn't be coerced into allocating arbitrary memory, but an unbounded `offset` still takes an unbounded time to walk past and CodeQL had no way to see that. Clamp `offset` to `MAX_PAGE_OFFSET = 1_000_000` (beyond our largest expected run count by several orders of magnitude) in both the shared `paginate_items` helper and the models list handler that rolls its own pagination. `limit` was already clamped to 100. Closes code-scanning alert #27. Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/crates/fabro-server/src/server.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/crates/fabro-server/src/server.rs b/lib/crates/fabro-server/src/server.rs index ee120d827..81fe5bebe 100644 --- a/lib/crates/fabro-server/src/server.rs +++ b/lib/crates/fabro-server/src/server.rs @@ -2711,9 +2711,11 @@ async fn board_run_metadata( metadata } +const MAX_PAGE_OFFSET: u32 = 1_000_000; + fn paginate_items(items: Vec, pagination: &PaginationParams) -> (Vec, bool) { let limit = pagination.limit.clamp(1, 100) as usize; - let offset = pagination.offset as usize; + let offset = pagination.offset.min(MAX_PAGE_OFFSET) as usize; let mut data: Vec<_> = items.into_iter().skip(offset).take(limit + 1).collect(); let has_more = data.len() > limit; data.truncate(limit); @@ -6326,7 +6328,7 @@ async fn list_models( let query = params.query.as_ref().map(|value| value.to_lowercase()); let limit = params.limit.clamp(1, 100) as usize; - let offset = params.offset as usize; + let offset = params.offset.min(MAX_PAGE_OFFSET) as usize; let mut models = fabro_model::Catalog::builtin() .list(provider)