Closes #832.
This PR implements the security/perf fix for the unbounded get_archived_reports
reader in the reporting contract. The reader now returns at most
DEFAULT_PAGE_LIMIT (20) entries (closing the latent host-budget DoS) and is
formally deprecated in favor of the already-paginated
get_archived_reports_page reader, which now follows the canonical terminator
convention (next_cursor == 0).
get_archived_reports(env, user)now delegates toget_archived_reports_page(user, 0, DEFAULT_PAGE_LIMIT)and returns at most the firstDEFAULT_PAGE_LIMIT(20) entries. The signature is preserved for back-compat but the function is marked#[deprecated].get_archived_reports_page(env, user, cursor, limit):- Out-of-range cursors (
cursor >= count) and empty archives now returnnext_cursor == 0(canonical terminator) instead of echoing the cursor back. limitis normalized viaremitwise_common::clamp_limit:0→DEFAULT_PAGE_LIMIT(20); values aboveMAX_PAGE_LIMIT(50) are clamped toMAX_PAGE_LIMIT.- Cursor termination is now guaranteed across all inputs (in-range, out-of-range, empty archive, oversized limit).
- Out-of-range cursors (
| File | Change |
|---|---|
reporting/src/lib.rs |
Imported DEFAULT_PAGE_LIMIT; marked get_archived_reports #[deprecated] and delegated to the paged reader; tightened get_archived_reports_page to use the canonical terminator and clamp_limit normalization; updated doc comments. |
reporting/src/tests_archived_pagination_bound.rs |
New module. 8 tests covering bound enforcement, first-page equivalence (deprecated vs paged), full archival traversal, out-of-range cursor, empty archive, limit=0 normalization, limit=u32::MAX clamping, and user isolation under bound. |
CHANGELOG_CONTRACTS.md |
New ## Reporting → ### v0.2.0 entry above the existing v0.1.0. Documents the bound, deprecation, terminator convention, migration, and #832 link. |
reporting/README.md |
Replaced the get_archived_reports row under Admin Maintenance with get_archived_reports_page including pagination contract and a get_archived_reports deprecation pointer (Issue #832) pointing back at the paged API. The deprecated entry remains in the Authorization Model table for grep discoverability. |
| Requirement | Status |
|---|---|
get_archived_reports no longer unbounded |
✅ capped at DEFAULT_PAGE_LIMIT (20) via delegation to the paged reader |
| Paged reader verified terminating + non-panicking | ✅ tests_archived_pagination_bound.rs::paged_reader_walks_entire_archive_and_terminates, ::paged_reader_out_of_range_cursor_returns_empty_page_with_terminator, ::paged_reader_empty_archive_returns_terminator |
| Deprecation noted in changelog + docs | ✅ CHANGELOG_CONTRACTS.md v0.2.0 + reporting/README.md deprecation note |
| Test coverage | ✅ 8 new tests in reporting/src/tests_archived_pagination_bound.rs exercising the bound terminator, normalization, equivalence, and user isolation |
cargo test -p reporting + clippy clean |
Required: re-run on a host with cargo installed |
Replace calls to get_archived_reports(user) with the canonical paged walk:
let mut cursor = 0u32;
loop {
let page = client.get_archived_reports_page(&user, &cursor, &DEFAULT_PAGE_LIMIT);
// ... process page.items ...
if page.next_cursor == 0 { break; }
cursor = page.next_cursor;
}No storage migration is required. The signature of the deprecated reader is unchanged, so existing callers that only inspect the first page (≤ 20 entries) keep working without code changes.
The bound is implemented by delegation, not by duplicating the loop logic.
This guarantees a single source of truth for the cursor/limit/index walk and
removes any drift risk between the two readers. The paged reader's limit
is normalized via remitwise_common::clamp_limit to match every other
paginated read in the Remitwise suite (docs/pagination-limit-contract.md).
cargo test -p reporting
cargo clippy -p reporting --no-deps --all-targets -- -D warnings
cargo fmt --checkNote: the
deny(clippy::unwrap_used)anddeny(clippy::expect_used)attributes inlib.rsapply only outside#[cfg(test)], so the affected legacy callers intests.rs/tests_updated.rs/tests_auth_acl.rsproduce only warnings (not errors) when they call the now-deprecatedget_archived_reports. Tests still pass without#[allow(deprecated)], but those warnings can be silenced in a follow-up cleanup if desired.
Closes #832