Skip to content

fix: add pagination and validation to messages query to prevent memory exhaustion#111

Merged
mubarakcoded merged 20 commits intomasterfrom
69-issue-z-unbounded-function-messages-query-enables-memory-exhaustion-denial-of-service
Jan 7, 2026
Merged

fix: add pagination and validation to messages query to prevent memory exhaustion#111
mubarakcoded merged 20 commits intomasterfrom
69-issue-z-unbounded-function-messages-query-enables-memory-exhaustion-denial-of-service

Conversation

@mubarakcoded
Copy link
Contributor

@mubarakcoded mubarakcoded commented Jan 2, 2026

Summary by CodeRabbit

  • New Features

    • Optional paginated message retrieval with a public Pagination control; pagination exposed to foreign-language bindings (limit/offset).
  • Bug Fixes

    • Enforced/validated pagination bounds (default and max limits) to prevent memory exhaustion; message lists are newest-first and paged.
    • Pending welcomes now support pagination.
    • Removed sensitive group identifiers from error outputs.
  • Breaking Changes

    • Message and pending-welcome retrieval APIs now accept an optional pagination argument; callers must pass None or provide pagination.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 2, 2026

Walkthrough

Group message retrieval was converted to a paginated API: a public Pagination type plus DEFAULT_MESSAGE_LIMIT/MAX_MESSAGE_LIMIT were added, GroupStorage::messages now accepts Option<Pagination>. Storage implementations, core API, UniFFI bindings, tests, examples, and changelogs were updated to support pagination and validate limits.

Changes

Cohort / File(s) Summary
Trait, types & constants
crates/mdk-storage-traits/src/groups/mod.rs, crates/mdk-storage-traits/CHANGELOG.md
Added Pagination { limit, offset }, DEFAULT_MESSAGE_LIMIT, MAX_MESSAGE_LIMIT; changed GroupStorage::messages signature to include pagination: Option<Pagination>; updated docs and examples.
Memory storage implementation
crates/mdk-memory-storage/src/groups.rs, crates/mdk-memory-storage/CHANGELOG.md
messages() now accepts Option<Pagination>; validates limit ≤ MAX_MESSAGE_LIMIT, verifies group existence, sorts by created_at DESC, applies offset/limit slicing, returns errors for invalid params/missing groups; tests added/updated.
SQLite storage implementation
crates/mdk-sqlite-storage/src/groups.rs, crates/mdk-sqlite-storage/CHANGELOG.md
messages() now accepts Option<Pagination>; validates limit, checks group existence before querying, SQL updated to include ORDER BY created_at DESC LIMIT ? OFFSET ?; enforces bounds and returns InvalidParameters on bad limits; tests added/updated.
Core API & examples
crates/mdk-core/src/messages.rs, crates/mdk-core/CHANGELOG.md, crates/mdk-core/examples/mls_memory.rs, crates/mdk-core/examples/mls_sqlite.rs
get_messages() signature changed to accept Option<Pagination> and forwards it to storage; examples and tests updated to pass None for defaults.
Tests & shared utilities
crates/mdk-storage-traits/src/test_utils.rs, crates/mdk-storage-traits/tests/shared/group_tests.rs
Test utilities and shared tests updated to call messages(..., None); tests extended to cover pagination semantics, ordering, boundaries, invalid limits, non-existent groups, empty groups, and large offsets.
UniFFI & FFI bindings
crates/mdk-uniffi/src/lib.rs, crates/mdk-uniffi/CHANGELOG.md
UniFFI API changed: get_messages(mls_group_id: String, limit: Option<u32>, offset: Option<u32>); constructs Pagination and forwards to core; bindings/tests updated.
Changelogs across crates
crates/*/CHANGELOG.md
Multiple changelogs updated documenting the breaking API change (pagination), added limits/constants, and security rationale to prevent unbounded message loading.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately summarizes the main change: adding pagination and validation to the messages query to prevent memory exhaustion, which is the primary objective documented in the PR summary.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/mdk-sqlite-storage/src/groups.rs (1)

378-380: Consider moving imports to the top of the test module.

Per coding guidelines, use statements should be placed at the top of the module scope rather than inside functions. These imports could be moved alongside the existing imports at lines 319-322.

🔎 Suggested refactor
 #[cfg(test)]
 mod tests {
     use mdk_storage_traits::groups::types::GroupState;
+    use mdk_storage_traits::messages::MessageStorage;
+    use mdk_storage_traits::messages::types::{Message, MessageState};
+    use nostr::{EventId, Kind, PublicKey, Tags, Timestamp, UnsignedEvent};
     use mdk_storage_traits::test_utils::crypto_utils::generate_random_bytes;

     use super::*;

     // ... existing tests ...

     #[test]
     fn test_messages_pagination() {
-        use mdk_storage_traits::messages::MessageStorage;
-        use mdk_storage_traits::messages::types::{Message, MessageState};
-        use nostr::{EventId, Kind, PublicKey, Tags, Timestamp, UnsignedEvent};
-
         let storage = MdkSqliteStorage::new_in_memory().unwrap();
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fedc362 and c27defc.

📒 Files selected for processing (6)
  • crates/mdk-memory-storage/CHANGELOG.md
  • crates/mdk-memory-storage/src/groups.rs
  • crates/mdk-sqlite-storage/CHANGELOG.md
  • crates/mdk-sqlite-storage/src/groups.rs
  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-storage-traits/src/groups/mod.rs
🧰 Additional context used
📓 Path-based instructions (3)
crates/*/CHANGELOG.md

📄 CodeRabbit inference engine (AGENTS.md)

crates/*/CHANGELOG.md: Every change that modifies functionality must update the appropriate crate CHANGELOG.md file (mdk-core, mdk-storage-traits, mdk-memory-storage, or mdk-sqlite-storage)
Follow Keep a Changelog format with sections for Unreleased, Breaking changes, Changed, Added, Fixed, Removed, and Deprecated in CHANGELOG files
Always include a PR reference link in changelog entries using the format ([#123](https://github.com/marmot-protocol/mdk/pull/123))

Files:

  • crates/mdk-memory-storage/CHANGELOG.md
  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-sqlite-storage/CHANGELOG.md
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

Never use bare URLs in markdown files. All URLs must be properly formatted: use [link text](url) for inline links, <url> for standalone URLs, ([#123](https://github.com/marmot-protocol/mdk/pull/123)) for PR/issue references, and ([abc1234](https://github.com/.../commit/abc1234...)) for commit references

Files:

  • crates/mdk-memory-storage/CHANGELOG.md
  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-sqlite-storage/CHANGELOG.md
**/*.rs

📄 CodeRabbit inference engine (.cursor/rules/rust.mdc)

**/*.rs: All trait bounds should be written in where clauses rather than inline with type parameters in Rust
Use Self instead of the explicit type name when referring to the type in impl blocks
Derive Debug, Clone, Copy, PartialEq, Eq, and Hash for public types when possible, in that order
Derive Default for public types when there is a reasonable default value
Always use full paths for logging with tracing::<op>!(...) instead of importing and using <op>!(...)
Prefer .to_string() or .to_owned() for &str to String conversion instead of .into() or String::from()
Order imports in the following sequence: (1) core/alloc/std, (2) external crates, (3) current sub-modules with mod declarations, (4) internal crate modules
Import the fmt module rather than using full paths when implementing traits from core::fmt or std::fmt
Use relative self:: imports for sub-modules rather than bare module names
Place all use statements at the top of the file following the specified import order, never inside functions, methods, or other blocks
Avoid if let ... { } else { } constructs; use match instead when both branches are non-empty
Use if let ... { } without an else clause when a match arm would be intentionally empty
Define sub-modules using separate files (e.g., x.rs) with mod x; declaration rather than inline mod x { .. } blocks, except for tests and benches modules
Use inline mod tests { #[cfg(test)] ... } for test modules rather than creating separate tests.rs files
Use inline mod benches { #[cfg(bench)] ... } for benchmark modules rather than creating separate benches.rs files

**/*.rs: All trait bounds must be placed in where clauses, not inline with generic parameters in Rust code
Use Self instead of the type name when possible in Rust code
Derive macros must be ordered as: Debug, Clone, Copy, PartialEq, Eq, Hash in Rust code
Always use tracing::warn!(...) with explicit module path, never im...

Files:

  • crates/mdk-sqlite-storage/src/groups.rs
  • crates/mdk-storage-traits/src/groups/mod.rs
  • crates/mdk-memory-storage/src/groups.rs
🧠 Learnings (2)
📚 Learning: 2026-01-01T16:28:05.759Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-01T16:28:05.759Z
Learning: Applies to crates/*/CHANGELOG.md : Every change that modifies functionality must update the appropriate crate CHANGELOG.md file (mdk-core, mdk-storage-traits, mdk-memory-storage, or mdk-sqlite-storage)

Applied to files:

  • crates/mdk-memory-storage/CHANGELOG.md
  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-storage-traits/src/groups/mod.rs
  • crates/mdk-sqlite-storage/CHANGELOG.md
📚 Learning: 2026-01-01T15:51:58.505Z
Learnt from: dannym-arx
Repo: marmot-protocol/mdk PR: 98
File: crates/mdk-core/CHANGELOG.md:46-46
Timestamp: 2026-01-01T15:51:58.505Z
Learning: In CHANGELOG.md files, keep the GitHub issue references (e.g., #61) as-is; do not convert '#61' (with no space) into an MD heading '# 61'. The hash without a space is intentional for GitHub auto-linking. This guideline applies to all CHANGELOG.md files in the repo.

Applied to files:

  • crates/mdk-memory-storage/CHANGELOG.md
  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-sqlite-storage/CHANGELOG.md
🧬 Code graph analysis (1)
crates/mdk-sqlite-storage/src/groups.rs (5)
crates/mdk-memory-storage/src/groups.rs (2)
  • messages_paginated (63-101)
  • messages (54-61)
crates/mdk-storage-traits/src/groups/mod.rs (2)
  • messages_paginated (86-91)
  • messages (50-50)
crates/mdk-storage-traits/src/group_id.rs (2)
  • as_slice (16-18)
  • from_slice (11-13)
crates/mdk-sqlite-storage/src/db.rs (5)
  • row_to_message (205-250)
  • from (24-26)
  • from (30-32)
  • from (66-68)
  • from (72-74)
crates/mdk-storage-traits/src/test_utils.rs (1)
  • generate_random_bytes (8-13)
🔇 Additional comments (13)
crates/mdk-storage-traits/CHANGELOG.md (1)

34-34: LGTM on added entries for pagination support.

Lines 34 and 39 properly document the new messages_paginated() method and the security fix with correct PR references and markdown formatting.

Also applies to: 39-39

crates/mdk-memory-storage/CHANGELOG.md (1)

34-34: LGTM on pagination implementation entries.

Both entries properly document the implementation of messages_paginated() and the security fix with correct PR references and markdown formatting.

Also applies to: 38-38

crates/mdk-sqlite-storage/CHANGELOG.md (1)

34-34: LGTM on SQLite pagination implementation entries.

Both entries properly document the implementation of messages_paginated() and the security fix with correct PR references and markdown formatting.

Also applies to: 38-38

crates/mdk-sqlite-storage/src/groups.rs (3)

127-134: LGTM!

The delegation pattern is clean and maintains backward compatibility by using the default limit and offset of 0.


136-182: LGTM!

The implementation correctly:

  • Validates limit bounds before querying
  • Checks group existence with a clear error message
  • Uses parameterized SQL with LIMIT ? OFFSET ? preventing SQL injection
  • Orders by created_at DESC consistent with the memory storage implementation
  • Safely casts usize to i64 (well within bounds given MAX_MESSAGE_LIMIT = 10000)

433-488: Good test coverage for pagination edge cases.

The tests thoroughly cover:

  • Pagination boundaries (pages 1, 2, 3 with proper offsets)
  • Ordering verification (newest first)
  • Non-overlapping pages
  • Offset beyond available data returning empty
  • Invalid limit values (0 and exceeding MAX)
  • Non-existent group error handling
crates/mdk-storage-traits/src/groups/mod.rs (3)

22-26: LGTM! Well-chosen default values.

The constants are appropriately sized:

  • DEFAULT_MESSAGE_LIMIT = 1000 provides a reasonable default for typical use cases
  • MAX_MESSAGE_LIMIT = 10000 prevents memory exhaustion while allowing flexibility

These values align with the PR objective of preventing denial-of-service through unbounded queries.


46-50: Good addition of the memory warning.

The warning clearly communicates the risk and directs users to the safer paginated alternative.


52-91: Excellent documentation for the new trait method.

The doc comments are comprehensive, covering:

  • Clear parameter descriptions with validation rules
  • Return value semantics (newest first ordering)
  • All error conditions documented
  • Practical usage example

This provides excellent guidance for implementers and consumers of this trait.

crates/mdk-memory-storage/src/groups.rs (4)

54-61: LGTM!

Consistent with the SQLite implementation - delegates to the paginated version with default limit.


88-96: LGTM!

The pagination logic is correct:

  • start = offset.min(messages.len()) - safely handles offset beyond array bounds
  • end = (offset + limit).min(messages.len()) - safely clamps to available messages
  • Slicing [start..end] returns empty when start == end (offset beyond data)

The in-memory sort is necessary since LRU cache doesn't maintain order, and this aligns with the SQLite ORDER BY created_at DESC.


189-196: Good test module structure.

The test module correctly follows the coding guidelines:

  • Inline mod tests with #[cfg(test)] as required
  • Imports placed at the top of the module scope

198-337: Comprehensive test coverage.

The test thoroughly validates:

  1. Default limit behavior
  2. Pagination across multiple pages
  3. Descending order by created_at
  4. Non-overlapping pages
  5. Offset beyond available data
  6. Invalid limit values (0 and exceeding MAX)
  7. Non-existent group error
  8. Empty group with no messages

This provides excellent confidence in the implementation correctness.

@github-actions
Copy link

github-actions bot commented Jan 2, 2026

✅ Coverage: 86.35% → 86.55% (+0.20%)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
crates/mdk-storage-traits/CHANGELOG.md (1)

35-35: PR reference is missing (already flagged in previous review).

This line is missing the required PR reference link that should be appended in the format ([#111](https://github.com/marmot-protocol/mdk/pull/111)).

🧹 Nitpick comments (1)
crates/mdk-sqlite-storage/src/groups.rs (1)

159-205: Consider validating the offset parameter.

While the limit parameter is properly validated (lines 166-172), the offset parameter is not. Although passing excessively large offset values would simply return an empty result set rather than cause errors, adding validation would make the API more robust and provide clearer feedback to callers.

🔎 Proposed validation for offset parameter
     ) -> Result<Vec<Message>, GroupError> {
         // Validate limit is within allowed range
         if !(1..=mdk_storage_traits::groups::MAX_MESSAGE_LIMIT).contains(&limit) {
             return Err(GroupError::InvalidParameters(format!(
                 "Limit must be between 1 and {}, got {}",
                 mdk_storage_traits::groups::MAX_MESSAGE_LIMIT,
                 limit
             )));
         }
+
+        // Validate offset is reasonable
+        if offset > 1_000_000 {
+            return Err(GroupError::InvalidParameters(format!(
+                "Offset {} exceeds reasonable maximum",
+                offset
+            )));
+        }
 
         // First verify the group exists
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c27defc and b4bd19d.

📒 Files selected for processing (4)
  • crates/mdk-memory-storage/CHANGELOG.md
  • crates/mdk-sqlite-storage/CHANGELOG.md
  • crates/mdk-sqlite-storage/src/groups.rs
  • crates/mdk-storage-traits/CHANGELOG.md
✅ Files skipped from review due to trivial changes (1)
  • crates/mdk-memory-storage/CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/mdk-sqlite-storage/CHANGELOG.md
🧰 Additional context used
📓 Path-based instructions (3)
crates/*/CHANGELOG.md

📄 CodeRabbit inference engine (AGENTS.md)

crates/*/CHANGELOG.md: Every change that modifies functionality must update the appropriate crate CHANGELOG.md file (mdk-core, mdk-storage-traits, mdk-memory-storage, or mdk-sqlite-storage)
Follow Keep a Changelog format with sections for Unreleased, Breaking changes, Changed, Added, Fixed, Removed, and Deprecated in CHANGELOG files
Always include a PR reference link in changelog entries using the format ([#123](https://github.com/marmot-protocol/mdk/pull/123))

Files:

  • crates/mdk-storage-traits/CHANGELOG.md
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

Never use bare URLs in markdown files. All URLs must be properly formatted: use [link text](url) for inline links, <url> for standalone URLs, ([#123](https://github.com/marmot-protocol/mdk/pull/123)) for PR/issue references, and ([abc1234](https://github.com/.../commit/abc1234...)) for commit references

Files:

  • crates/mdk-storage-traits/CHANGELOG.md
**/*.rs

📄 CodeRabbit inference engine (.cursor/rules/rust.mdc)

**/*.rs: All trait bounds should be written in where clauses rather than inline with type parameters in Rust
Use Self instead of the explicit type name when referring to the type in impl blocks
Derive Debug, Clone, Copy, PartialEq, Eq, and Hash for public types when possible, in that order
Derive Default for public types when there is a reasonable default value
Always use full paths for logging with tracing::<op>!(...) instead of importing and using <op>!(...)
Prefer .to_string() or .to_owned() for &str to String conversion instead of .into() or String::from()
Order imports in the following sequence: (1) core/alloc/std, (2) external crates, (3) current sub-modules with mod declarations, (4) internal crate modules
Import the fmt module rather than using full paths when implementing traits from core::fmt or std::fmt
Use relative self:: imports for sub-modules rather than bare module names
Place all use statements at the top of the file following the specified import order, never inside functions, methods, or other blocks
Avoid if let ... { } else { } constructs; use match instead when both branches are non-empty
Use if let ... { } without an else clause when a match arm would be intentionally empty
Define sub-modules using separate files (e.g., x.rs) with mod x; declaration rather than inline mod x { .. } blocks, except for tests and benches modules
Use inline mod tests { #[cfg(test)] ... } for test modules rather than creating separate tests.rs files
Use inline mod benches { #[cfg(bench)] ... } for benchmark modules rather than creating separate benches.rs files

**/*.rs: All trait bounds must be placed in where clauses, not inline with generic parameters in Rust code
Use Self instead of the type name when possible in Rust code
Derive macros must be ordered as: Debug, Clone, Copy, PartialEq, Eq, Hash in Rust code
Always use tracing::warn!(...) with explicit module path, never im...

Files:

  • crates/mdk-sqlite-storage/src/groups.rs
🧠 Learnings (3)
📚 Learning: 2026-01-01T16:28:05.759Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-01T16:28:05.759Z
Learning: Applies to crates/*/CHANGELOG.md : Every change that modifies functionality must update the appropriate crate CHANGELOG.md file (mdk-core, mdk-storage-traits, mdk-memory-storage, or mdk-sqlite-storage)

Applied to files:

  • crates/mdk-storage-traits/CHANGELOG.md
📚 Learning: 2026-01-01T16:28:05.759Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-01T16:28:05.759Z
Learning: Applies to crates/*/CHANGELOG.md : Always include a PR reference link in changelog entries using the format `([#123](https://github.com/marmot-protocol/mdk/pull/123))`

Applied to files:

  • crates/mdk-storage-traits/CHANGELOG.md
📚 Learning: 2026-01-01T15:51:58.505Z
Learnt from: dannym-arx
Repo: marmot-protocol/mdk PR: 98
File: crates/mdk-core/CHANGELOG.md:46-46
Timestamp: 2026-01-01T15:51:58.505Z
Learning: In CHANGELOG.md files, keep the GitHub issue references (e.g., #61) as-is; do not convert '#61' (with no space) into an MD heading '# 61'. The hash without a space is intentional for GitHub auto-linking. This guideline applies to all CHANGELOG.md files in the repo.

Applied to files:

  • crates/mdk-storage-traits/CHANGELOG.md
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test Coverage
🔇 Additional comments (2)
crates/mdk-sqlite-storage/src/groups.rs (2)

150-157: LGTM! Backward-compatible delegation.

The delegation to messages_paginated with the default limit maintains backward compatibility while adding the safety of bounded queries.


472-579: Excellent test coverage for pagination.

The test comprehensively covers:

  • Multiple pages with proper offset handling
  • Ordering (newest first)
  • Edge cases (zero limit, exceeding MAX, non-existent group, offset beyond data)
  • No overlap between pages
  • Backward compatibility via default messages() method

@github-actions
Copy link

github-actions bot commented Jan 2, 2026

✅ Coverage: 86.35% → 86.58% (+0.23%)

Copy link
Contributor

@jgmontoya jgmontoya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly to #110 (review): the get_messages method in crates/mdk-core/src/messages.rs used to return all messages, and now returns just a single page (undocumented breaking change).

As before (although here I think it's way more relevant/important), it's weird that there's no way for a user to access the different pages.

@dannym-arx
Copy link
Contributor

my comments here are also basically the same as on my review of #110

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
crates/mdk-storage-traits/src/groups/mod.rs (2)

28-35: Missing PartialEq and Eq derives per coding guidelines.

The Pagination struct is a public type and should derive PartialEq and Eq for comparison operations. As per coding guidelines, derive order should be: Debug, Clone, Copy, PartialEq, Eq, Hash.

🔎 Proposed fix
 /// Pagination parameters for querying messages
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub struct Pagination {
     /// Maximum number of messages to return
     pub limit: Option<usize>,
     /// Number of messages to skip
     pub offset: Option<usize>,
 }

Based on coding guidelines: "Derive Debug, Clone, Copy, PartialEq, Eq, and Hash for public types when possible, in that order"


54-61: Consider simplifying by deriving Default.

The accessor methods (limit() and offset()) already provide fallback values via unwrap_or. Deriving Default would yield Pagination { limit: None, offset: None }, which produces identical behavior through the accessors. This would reduce boilerplate.

🔎 Proposed simplification
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
 pub struct Pagination {
     ...
 }
-
-impl Default for Pagination {
-    fn default() -> Self {
-        Self {
-            limit: Some(DEFAULT_MESSAGE_LIMIT),
-            offset: Some(0),
-        }
-    }
-}

If explicit values in Default are preferred for documentation/debugging purposes, the current approach is also fine.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b4bd19d and ee64d24.

📒 Files selected for processing (10)
  • crates/mdk-core/CHANGELOG.md
  • crates/mdk-core/src/messages.rs
  • crates/mdk-memory-storage/CHANGELOG.md
  • crates/mdk-memory-storage/src/groups.rs
  • crates/mdk-sqlite-storage/CHANGELOG.md
  • crates/mdk-sqlite-storage/src/groups.rs
  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-storage-traits/src/groups/mod.rs
  • crates/mdk-storage-traits/src/test_utils.rs
  • crates/mdk-storage-traits/tests/shared/group_tests.rs
🧰 Additional context used
📓 Path-based instructions (3)
**/*.rs

📄 CodeRabbit inference engine (.cursor/rules/rust.mdc)

**/*.rs: All trait bounds should be written in where clauses rather than inline with type parameters in Rust
Use Self instead of the explicit type name when referring to the type in impl blocks
Derive Debug, Clone, Copy, PartialEq, Eq, and Hash for public types when possible, in that order
Derive Default for public types when there is a reasonable default value
Always use full paths for logging with tracing::<op>!(...) instead of importing and using <op>!(...)
Prefer .to_string() or .to_owned() for &str to String conversion instead of .into() or String::from()
Order imports in the following sequence: (1) core/alloc/std, (2) external crates, (3) current sub-modules with mod declarations, (4) internal crate modules
Import the fmt module rather than using full paths when implementing traits from core::fmt or std::fmt
Use relative self:: imports for sub-modules rather than bare module names
Place all use statements at the top of the file following the specified import order, never inside functions, methods, or other blocks
Avoid if let ... { } else { } constructs; use match instead when both branches are non-empty
Use if let ... { } without an else clause when a match arm would be intentionally empty
Define sub-modules using separate files (e.g., x.rs) with mod x; declaration rather than inline mod x { .. } blocks, except for tests and benches modules
Use inline mod tests { #[cfg(test)] ... } for test modules rather than creating separate tests.rs files
Use inline mod benches { #[cfg(bench)] ... } for benchmark modules rather than creating separate benches.rs files

**/*.rs: All trait bounds in generics must be placed in where clauses, not inline
Use Self instead of the type name when possible
Derive order must be: Debug, Clone, Copy, PartialEq, Eq, Hash (in this order)
Always use tracing::warn!(...), never import and use warn!(...)
Use .to_string() or .to_owned() for string ...

Files:

  • crates/mdk-storage-traits/tests/shared/group_tests.rs
  • crates/mdk-storage-traits/src/test_utils.rs
  • crates/mdk-core/src/messages.rs
  • crates/mdk-sqlite-storage/src/groups.rs
  • crates/mdk-memory-storage/src/groups.rs
  • crates/mdk-storage-traits/src/groups/mod.rs
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

Never use bare URLs in markdown files; all URLs must be properly formatted using [link text](url), <url>, or ([#123](url)) for PR/issue references

Files:

  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-sqlite-storage/CHANGELOG.md
  • crates/mdk-core/CHANGELOG.md
  • crates/mdk-memory-storage/CHANGELOG.md
crates/*/CHANGELOG.md

📄 CodeRabbit inference engine (AGENTS.md)

Every crate CHANGELOG entry must include a PR reference using the format ([#123](https://github.com/marmot-protocol/mdk/pull/123))

Files:

  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-sqlite-storage/CHANGELOG.md
  • crates/mdk-core/CHANGELOG.md
  • crates/mdk-memory-storage/CHANGELOG.md
🧠 Learnings (5)
📚 Learning: 2026-01-03T08:39:39.771Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T08:39:39.771Z
Learning: Every change that modifies functionality must update the CHANGELOG in the affected crate

Applied to files:

  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-sqlite-storage/CHANGELOG.md
  • crates/mdk-memory-storage/CHANGELOG.md
📚 Learning: 2026-01-03T08:39:39.771Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T08:39:39.771Z
Learning: Applies to crates/*/CHANGELOG.md : Every crate CHANGELOG entry must include a PR reference using the format `([#123](https://github.com/marmot-protocol/mdk/pull/123))`

Applied to files:

  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-sqlite-storage/CHANGELOG.md
  • crates/mdk-memory-storage/CHANGELOG.md
📚 Learning: 2026-01-01T15:51:58.505Z
Learnt from: dannym-arx
Repo: marmot-protocol/mdk PR: 98
File: crates/mdk-core/CHANGELOG.md:46-46
Timestamp: 2026-01-01T15:51:58.505Z
Learning: In CHANGELOG.md files, keep the GitHub issue references (e.g., #61) as-is; do not convert '#61' (with no space) into an MD heading '# 61'. The hash without a space is intentional for GitHub auto-linking. This guideline applies to all CHANGELOG.md files in the repo.

Applied to files:

  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-sqlite-storage/CHANGELOG.md
  • crates/mdk-core/CHANGELOG.md
  • crates/mdk-memory-storage/CHANGELOG.md
📚 Learning: 2025-12-03T10:18:10.432Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: .cursor/rules/rust.mdc:0-0
Timestamp: 2025-12-03T10:18:10.432Z
Learning: Applies to **/*.rs : Place all `use` statements at the top of the file following the specified import order, never inside functions, methods, or other blocks

Applied to files:

  • crates/mdk-sqlite-storage/src/groups.rs
📚 Learning: 2026-01-03T08:39:39.771Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T08:39:39.771Z
Learning: Applies to **/*.rs : Place all `use` statements at file top, never inside functions

Applied to files:

  • crates/mdk-sqlite-storage/src/groups.rs
🧬 Code graph analysis (1)
crates/mdk-storage-traits/src/groups/mod.rs (2)
crates/mdk-memory-storage/src/groups.rs (1)
  • messages (54-94)
crates/mdk-sqlite-storage/src/groups.rs (1)
  • messages (157-205)
🔇 Additional comments (14)
crates/mdk-sqlite-storage/CHANGELOG.md (1)

30-53: LGTM!

All CHANGELOG entries properly format PR references and follow the required ([#111](url)) pattern. Entries are well-organized across appropriate sections (Breaking changes, Changed, Added, Fixed, Removed).

crates/mdk-core/CHANGELOG.md (1)

36-44: LGTM!

PR references are properly formatted as ([#111](url)). Entries are placed in correct sections (Added and Fixed) and align with the pagination feature implementation.

crates/mdk-memory-storage/CHANGELOG.md (1)

30-46: LGTM!

All PR references properly formatted as ([#111](url)). Entries are consistently organized across sections matching the SQLite storage crate CHANGELOG, maintaining clarity about breaking changes, pagination support, and security fixes.

crates/mdk-storage-traits/src/test_utils.rs (1)

468-468: LGTM! Pagination parameter correctly added.

The call site has been properly updated to pass None for the pagination parameter, which aligns with the new messages trait signature and will use default pagination behavior.

crates/mdk-storage-traits/tests/shared/group_tests.rs (1)

351-351: LGTM! Test updated for new pagination API.

The test correctly exercises the updated messages method with None pagination, verifying default pagination behavior works as expected.

crates/mdk-storage-traits/CHANGELOG.md (1)

30-47: LGTM! CHANGELOG properly documented with PR references.

All entries correctly include PR references in the required format ([#111](https://github.com/marmot-protocol/mdk/pull/111)). The documentation clearly explains the breaking changes, security fix for memory exhaustion, and the removal of the offset cap.

crates/mdk-core/src/messages.rs (2)

15-15: LGTM! Import correctly placed.

The Pagination import is properly positioned at the file top with other imports from the storage traits module.


102-137: LGTM! Well-designed pagination API with backward compatibility.

The refactoring maintains backward compatibility by having get_messages delegate to get_messages_paginated with None. The new method is well-documented with clear examples showing how to use pagination. The implementation correctly delegates to the storage layer with the pagination parameter.

crates/mdk-sqlite-storage/src/groups.rs (3)

13-18: LGTM! Test imports correctly positioned with conditional compilation.

The test-only imports are properly placed at the file top with #[cfg(test)] attributes, following Rust conventions for conditional compilation. This addresses the previous review concern about imports inside functions.

Based on learnings, imports are correctly at file top.


157-205: LGTM! Solid pagination implementation with proper validation.

The implementation correctly:

  • Unwraps pagination with default behavior when None
  • Validates limit bounds (1 to MAX_MESSAGE_LIMIT)
  • Verifies group existence before querying
  • Uses parameterized SQL queries preventing injection
  • Orders results by created_at DESC for newest-first retrieval

The removal of offset validation is intentional per the CHANGELOG to support legitimate large-scale use cases.


467-591: LGTM! Excellent comprehensive test coverage.

The test thoroughly validates:

  • Basic pagination with multiple pages
  • Default behavior when pagination is None
  • Non-overlapping pages
  • Empty results beyond available messages
  • Error handling for invalid limits (0 and exceeding MAX)
  • Error for non-existent groups
  • Large offset handling (confirming no MAX_OFFSET restriction)

The comprehensive nature of this test is a significant strength, ensuring the pagination implementation is robust and correct.

crates/mdk-memory-storage/src/groups.rs (2)

54-93: LGTM! Pagination implementation is correct and well-structured.

The pagination logic properly handles edge cases:

  • Validation ensures limit is within bounds before processing
  • start and end indices are safely clamped to messages.len()
  • Group existence check prevents unnecessary work on invalid input
  • Sorting before pagination ensures consistent ordering

182-348: Comprehensive test coverage for pagination behavior.

The tests effectively validate:

  • Default pagination, explicit limits/offsets, boundary conditions
  • Error cases (zero limit, exceeding max, non-existent group)
  • Ordering guarantees and page non-overlap
  • Empty groups and large offsets
crates/mdk-storage-traits/src/groups/mod.rs (1)

80-114: Well-documented API with clear error semantics.

The trait method documentation is thorough—covers ordering guarantees, pagination defaults, error conditions, and includes usage examples. The signature is consistent with both memory and SQLite implementations.

@mubarakcoded
Copy link
Contributor Author

Similarly to #110 (review): the get_messages method in crates/mdk-core/src/messages.rs used to return all messages, and now returns just a single page (undocumented breaking change).

As before (although here I think it's way more relevant/important), it's weird that there's no way for a user to access the different pages.

@jgmontoya Thank you for catching this! I've documented the change in all relevant CHANGELOGs:
and users who need more than 1000 messages can now use the new get_messages_paginated() method to paginate through their entire message history.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
crates/mdk-storage-traits/CHANGELOG.md (1)

30-31: Changelog entries and PR references look consistent with guidelines

All new bullets correctly include the [#111](...) PR reference and avoid bare URLs; duplication of the MAX_MESSAGE_OFFSET removal under both “Breaking changes” and “Removed” is mildly redundant but acceptable as-is.

Also applies to: 37-38, 42-42, 46-46

crates/mdk-uniffi/src/lib.rs (1)

251-260: UniFFI get_messages correctly forwards None but consider exposing pagination

Forwarding None into mdk.get_messages(&group_id, None) matches the updated core API and keeps default behaviour. However, UniFFI callers now silently get only the default page, with no way to request additional pages or know about the cap. Consider:

  • Updating the doc comment to note that only the first page (default limit) is returned.
  • Adding a separate UniFFI-exported method that accepts explicit pagination so FFI clients can traverse full history.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ee64d24 and 0a7f5f0.

📒 Files selected for processing (6)
  • crates/mdk-core/CHANGELOG.md
  • crates/mdk-core/examples/mls_memory.rs
  • crates/mdk-core/examples/mls_sqlite.rs
  • crates/mdk-core/src/messages.rs
  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-uniffi/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/mdk-core/CHANGELOG.md
🧰 Additional context used
📓 Path-based instructions (3)
**/*.rs

📄 CodeRabbit inference engine (.cursor/rules/rust.mdc)

**/*.rs: All trait bounds should be written in where clauses rather than inline with type parameters in Rust
Use Self instead of the explicit type name when referring to the type in impl blocks
Derive Debug, Clone, Copy, PartialEq, Eq, and Hash for public types when possible, in that order
Derive Default for public types when there is a reasonable default value
Always use full paths for logging with tracing::<op>!(...) instead of importing and using <op>!(...)
Prefer .to_string() or .to_owned() for &str to String conversion instead of .into() or String::from()
Order imports in the following sequence: (1) core/alloc/std, (2) external crates, (3) current sub-modules with mod declarations, (4) internal crate modules
Import the fmt module rather than using full paths when implementing traits from core::fmt or std::fmt
Use relative self:: imports for sub-modules rather than bare module names
Place all use statements at the top of the file following the specified import order, never inside functions, methods, or other blocks
Avoid if let ... { } else { } constructs; use match instead when both branches are non-empty
Use if let ... { } without an else clause when a match arm would be intentionally empty
Define sub-modules using separate files (e.g., x.rs) with mod x; declaration rather than inline mod x { .. } blocks, except for tests and benches modules
Use inline mod tests { #[cfg(test)] ... } for test modules rather than creating separate tests.rs files
Use inline mod benches { #[cfg(bench)] ... } for benchmark modules rather than creating separate benches.rs files

**/*.rs: All trait bounds in generics must be placed in where clauses, not inline
Use Self instead of the type name when possible
Derive order must be: Debug, Clone, Copy, PartialEq, Eq, Hash (in this order)
Always use tracing::warn!(...), never import and use warn!(...)
Use .to_string() or .to_owned() for string ...

Files:

  • crates/mdk-core/examples/mls_memory.rs
  • crates/mdk-core/src/messages.rs
  • crates/mdk-core/examples/mls_sqlite.rs
  • crates/mdk-uniffi/src/lib.rs
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

Never use bare URLs in markdown files; all URLs must be properly formatted using [link text](url), <url>, or ([#123](url)) for PR/issue references

Files:

  • crates/mdk-storage-traits/CHANGELOG.md
crates/*/CHANGELOG.md

📄 CodeRabbit inference engine (AGENTS.md)

Every crate CHANGELOG entry must include a PR reference using the format ([#123](https://github.com/marmot-protocol/mdk/pull/123))

Files:

  • crates/mdk-storage-traits/CHANGELOG.md
🧠 Learnings (4)
📚 Learning: 2025-11-30T11:28:06.561Z
Learnt from: dannym-arx
Repo: marmot-protocol/mdk PR: 19
File: crates/mdk-uniffi/src/README.md:16-23
Timestamp: 2025-11-30T11:28:06.561Z
Learning: In the mdk-uniffi crate (Rust UniFFI bindings for MDK), multi-threading support is intentionally not provided. The Mdk instance should be used in a single-threaded context only.

Applied to files:

  • crates/mdk-uniffi/src/lib.rs
📚 Learning: 2026-01-03T08:39:39.771Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T08:39:39.771Z
Learning: Every change that modifies functionality must update the CHANGELOG in the affected crate

Applied to files:

  • crates/mdk-storage-traits/CHANGELOG.md
📚 Learning: 2026-01-03T08:39:39.771Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T08:39:39.771Z
Learning: Applies to crates/*/CHANGELOG.md : Every crate CHANGELOG entry must include a PR reference using the format `([#123](https://github.com/marmot-protocol/mdk/pull/123))`

Applied to files:

  • crates/mdk-storage-traits/CHANGELOG.md
📚 Learning: 2026-01-01T15:51:58.505Z
Learnt from: dannym-arx
Repo: marmot-protocol/mdk PR: 98
File: crates/mdk-core/CHANGELOG.md:46-46
Timestamp: 2026-01-01T15:51:58.505Z
Learning: In CHANGELOG.md files, keep the GitHub issue references (e.g., #61) as-is; do not convert '#61' (with no space) into an MD heading '# 61'. The hash without a space is intentional for GitHub auto-linking. This guideline applies to all CHANGELOG.md files in the repo.

Applied to files:

  • crates/mdk-storage-traits/CHANGELOG.md
🧬 Code graph analysis (3)
crates/mdk-core/examples/mls_memory.rs (5)
crates/mdk-core/src/messages.rs (1)
  • get_messages (117-125)
crates/mdk-uniffi/src/lib.rs (1)
  • get_messages (252-260)
crates/mdk-storage-traits/src/groups/mod.rs (1)
  • messages (110-114)
crates/mdk-memory-storage/src/groups.rs (1)
  • messages (54-94)
crates/mdk-sqlite-storage/src/groups.rs (1)
  • messages (157-205)
crates/mdk-core/src/messages.rs (4)
crates/mdk-uniffi/src/lib.rs (2)
  • get_messages (252-260)
  • result (371-379)
crates/mdk-storage-traits/src/groups/mod.rs (1)
  • messages (110-114)
crates/mdk-memory-storage/src/groups.rs (1)
  • messages (54-94)
crates/mdk-sqlite-storage/src/groups.rs (1)
  • messages (157-205)
crates/mdk-core/examples/mls_sqlite.rs (5)
crates/mdk-core/src/messages.rs (1)
  • get_messages (117-125)
crates/mdk-uniffi/src/lib.rs (1)
  • get_messages (252-260)
crates/mdk-storage-traits/src/groups/mod.rs (1)
  • messages (110-114)
crates/mdk-memory-storage/src/groups.rs (1)
  • messages (54-94)
crates/mdk-sqlite-storage/src/groups.rs (1)
  • messages (157-205)
🔇 Additional comments (6)
crates/mdk-core/examples/mls_memory.rs (1)

179-181: Example updates to get_messages align with new paginated API

All example call sites now pass the extra None parameter to get_messages, matching the new (&GroupId, Option<Pagination>) signature while preserving the intended behaviour in this small-message example.

Also applies to: 207-210, 222-224, 231-233

crates/mdk-core/examples/mls_sqlite.rs (1)

188-190: SQLite example get_messages calls correctly updated for pagination

Each example now passes None as the pagination argument, matching the updated API while leaving the example semantics unchanged for these small message sets.

Also applies to: 216-219, 231-235, 243-245

crates/mdk-core/src/messages.rs (4)

1091-1101: Tests now correctly exercise the default (None) pagination path

The updated test callsites:

  • test_get_messages_empty_group
  • test_get_messages_for_group
  • test_create_message_ensures_rumor_id

now use mdk.get_messages(&group_id, None), which matches the new API and validates that the default pagination (limit/offset from Pagination::default()) still behaves as the previous “get all messages” semantics for small groups. No changes needed here.

Also applies to: 1263-1295, 1395-1408


1706-1716: Non-existent group and multi-client tests correctly updated to use paginated get_messages

  • test_get_messages_nonexistent_group now calls get_messages(&non_existent_group_id, None) and asserts an error, which is consistent with storage backends returning InvalidParameters for unknown groups.
  • test_multi_client_message_synchronization uses get_messages(&group_id, None) for both Alice and Bob to verify synchronized histories; this remains valid since message counts are well below the default limit.

These updates correctly track the new API while preserving the intended assertions.

Also applies to: 2459-2479


2869-2880: Idempotency and ordering tests correctly adapted to paginated retrieval

In:

  • test_duplicate_message_from_multiple_relays
  • test_single_client_message_idempotency
  • test_message_processing_order_independence

all get_messages usages now pass None for pagination, and the tests still assert on small, bounded message counts. This ensures the idempotency and ordering guarantees are checked against the default page without relying on unbounded retrieval.

Also applies to: 2896-2937, 2953-2986


3189-3200: Epoch/extended-offline tests use the new API correctly

The updated get_messages(&group_id, None) calls in:

  • test_extended_offline_period_sync
  • test_message_processing_across_epochs

correctly align with the paginated API while continuing to assert on modest histories (≤ a few messages). These tests now explicitly validate that default pagination still returns the full logical history in these small-scale scenarios.

Also applies to: 3505-3524

@github-actions
Copy link

github-actions bot commented Jan 3, 2026

✅ Coverage: 86.6% → 86.85% (+0.25%)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aef0529 and 441d626.

📒 Files selected for processing (1)
  • crates/mdk-core/src/messages.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (.cursor/rules/rust.mdc)

**/*.rs: All trait bounds should be written in where clauses rather than inline with type parameters in Rust
Use Self instead of the explicit type name when referring to the type in impl blocks
Derive Debug, Clone, Copy, PartialEq, Eq, and Hash for public types when possible, in that order
Derive Default for public types when there is a reasonable default value
Always use full paths for logging with tracing::<op>!(...) instead of importing and using <op>!(...)
Prefer .to_string() or .to_owned() for &str to String conversion instead of .into() or String::from()
Order imports in the following sequence: (1) core/alloc/std, (2) external crates, (3) current sub-modules with mod declarations, (4) internal crate modules
Import the fmt module rather than using full paths when implementing traits from core::fmt or std::fmt
Use relative self:: imports for sub-modules rather than bare module names
Place all use statements at the top of the file following the specified import order, never inside functions, methods, or other blocks
Avoid if let ... { } else { } constructs; use match instead when both branches are non-empty
Use if let ... { } without an else clause when a match arm would be intentionally empty
Define sub-modules using separate files (e.g., x.rs) with mod x; declaration rather than inline mod x { .. } blocks, except for tests and benches modules
Use inline mod tests { #[cfg(test)] ... } for test modules rather than creating separate tests.rs files
Use inline mod benches { #[cfg(bench)] ... } for benchmark modules rather than creating separate benches.rs files

**/*.rs: All trait bounds in generics must be placed in where clauses, not inline
Use Self instead of the type name when possible
Derive order must be: Debug, Clone, Copy, PartialEq, Eq, Hash (in this order)
Always use tracing::warn!(...), never import and use warn!(...)
Use .to_string() or .to_owned() for string ...

Files:

  • crates/mdk-core/src/messages.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test Coverage
🔇 Additional comments (2)
crates/mdk-core/src/messages.rs (2)

117-125: API change correctly implemented.

The function signature change to accept Option<Pagination> is correctly implemented and properly delegates to the storage layer. The breaking change is documented in the CHANGELOG per the PR objectives, and passing None maintains the default behavior (retrieving the default page size).


88-116: Documentation examples are correct and properly address the previous concern.

The doc examples have been verified against the actual Pagination::new signature from mdk_storage_traits::groups. The calls to Pagination::new(Some(100), Some(0)) correctly match the constructor which accepts (limit: Option<usize>, offset: Option<usize>). The examples accurately demonstrate both default pagination (passing None) and custom pagination (passing Some(Pagination::new(...))), resolving the previous review comment's concern about mismatched signatures.

@github-actions
Copy link

github-actions bot commented Jan 3, 2026

✅ Coverage: 86.6% → 86.87% (+0.27%)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 441d626 and 94518b9.

📒 Files selected for processing (1)
  • crates/mdk-core/src/messages.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (.cursor/rules/rust.mdc)

**/*.rs: All trait bounds should be written in where clauses rather than inline with type parameters in Rust
Use Self instead of the explicit type name when referring to the type in impl blocks
Derive Debug, Clone, Copy, PartialEq, Eq, and Hash for public types when possible, in that order
Derive Default for public types when there is a reasonable default value
Always use full paths for logging with tracing::<op>!(...) instead of importing and using <op>!(...)
Prefer .to_string() or .to_owned() for &str to String conversion instead of .into() or String::from()
Order imports in the following sequence: (1) core/alloc/std, (2) external crates, (3) current sub-modules with mod declarations, (4) internal crate modules
Import the fmt module rather than using full paths when implementing traits from core::fmt or std::fmt
Use relative self:: imports for sub-modules rather than bare module names
Place all use statements at the top of the file following the specified import order, never inside functions, methods, or other blocks
Avoid if let ... { } else { } constructs; use match instead when both branches are non-empty
Use if let ... { } without an else clause when a match arm would be intentionally empty
Define sub-modules using separate files (e.g., x.rs) with mod x; declaration rather than inline mod x { .. } blocks, except for tests and benches modules
Use inline mod tests { #[cfg(test)] ... } for test modules rather than creating separate tests.rs files
Use inline mod benches { #[cfg(bench)] ... } for benchmark modules rather than creating separate benches.rs files

**/*.rs: All trait bounds in generics must be placed in where clauses, not inline
Use Self instead of the type name when possible
Derive order must be: Debug, Clone, Copy, PartialEq, Eq, Hash (in this order)
Always use tracing::warn!(...), never import and use warn!(...)
Use .to_string() or .to_owned() for string ...

Files:

  • crates/mdk-core/src/messages.rs
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T08:39:39.771Z
Learning: Applies to crates/*/CHANGELOG.md : Every crate CHANGELOG entry must include a PR reference using the format `([#123](https://github.com/marmot-protocol/mdk/pull/123))`
🧬 Code graph analysis (1)
crates/mdk-core/src/messages.rs (3)
crates/mdk-memory-storage/src/groups.rs (2)
  • messages (54-91)
  • admins (93-101)
crates/mdk-storage-traits/src/groups/mod.rs (3)
  • messages (110-114)
  • admins (117-117)
  • new (39-41)
crates/mdk-sqlite-storage/src/groups.rs (2)
  • messages (157-202)
  • admins (204-210)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test Coverage
🔇 Additional comments (4)
crates/mdk-core/src/messages.rs (4)

15-15: LGTM! Import follows coding guidelines.

The Pagination import is correctly placed with other mdk_storage_traits imports and follows the project's import ordering conventions.


88-125: LGTM! Well-documented API with backward compatibility.

The updated get_messages signature maintains backward compatibility by using Option<Pagination>, and the documentation provides clear examples for both default and paginated cases. The implementation correctly delegates to the storage layer.


1097-1099: LGTM! Test updates maintain backward compatibility.

All existing test calls to get_messages have been consistently updated to pass None for the pagination parameter, maintaining their original behavior while adapting to the new API signature.

Also applies to: 1280-1282, 1342-1344, 1464-1466, 2522-2528, 2776-2776, 2937-2939, 2995-2997, 3041-3043, 3259-3259, 3467-3467, 3574-3575


2533-2544: LGTM! Improved test robustness.

These changes enhance test stability by acknowledging that messages with equal timestamps may have non-deterministic ordering. Using content-based verification (contains()) instead of index-based assertions makes the tests more reliable.

Also applies to: 3268-3279

@github-actions
Copy link

github-actions bot commented Jan 6, 2026

✅ Coverage: 86.88% → 87.2% (+0.32%)

@mubarakcoded mubarakcoded force-pushed the 69-issue-z-unbounded-function-messages-query-enables-memory-exhaustion-denial-of-service branch from 105b944 to 3f2c718 Compare January 6, 2026 18:24
@github-actions
Copy link

github-actions bot commented Jan 6, 2026

✅ Coverage: 86.88% → 87.42% (+0.54%)

@mubarakcoded mubarakcoded force-pushed the 69-issue-z-unbounded-function-messages-query-enables-memory-exhaustion-denial-of-service branch from a9548ec to d648c69 Compare January 6, 2026 18:39
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/mdk-memory-storage/CHANGELOG.md (1)

30-55: Fix duplicate section headers and reposition the breaking change for pending_welcomes().

The changelog has structural issues:

  1. Duplicate section headers: ### Changed, ### Added, and ### Fixed appear twice (lines 32, 45; lines 36, 49; lines 40, 53). Each section should appear only once.
  2. Misplaced breaking change: Line 43 contains a **BREAKING**: entry for pending_welcomes() (PR #110), but it's positioned under the ### Fixed section instead of the ### Breaking changes section.

The changelog should have a single set of section headers, with all entries for PRs #111 and #110 grouped under their respective sections.

🔎 Proposed fix: Consolidate sections and reposition breaking changes
## Unreleased

### Breaking changes

- **BREAKING**: Updated `messages()` implementation to accept `Option<Pagination>` parameter ([#111](https://github.com/marmot-protocol/mdk/pull/111))
- **BREAKING**: Updated `pending_welcomes()` implementation to accept `Option<Pagination>` parameter ([#110](https://github.com/marmot-protocol/mdk/pull/110))

### Changed

- Simplified validation logic to use range contains pattern for better readability ([#111](https://github.com/marmot-protocol/mdk/pull/111))
- Simplified validation logic to use range contains pattern for better readability ([#110](https://github.com/marmot-protocol/mdk/pull/110))

### Added

- Implemented pagination support using `Pagination` struct for group messages ([#111](https://github.com/marmot-protocol/mdk/pull/111))
- Implemented pagination support using `Pagination` struct for pending welcomes ([#110](https://github.com/marmot-protocol/mdk/pull/110))

### Fixed

- **Security (Audit Issue Z)**: Added pagination to prevent memory exhaustion from unbounded loading of group messages ([#111](https://github.com/marmot-protocol/mdk/pull/111))
- **Security (Audit Issue AA)**: Added pagination to prevent memory exhaustion from unbounded loading of pending welcomes ([#110](https://github.com/marmot-protocol/mdk/pull/110))
- **Security (Audit Issue AO)**: Removed MLS group identifiers from error messages to prevent metadata leakage in logs and telemetry. Error messages now use generic "Group not found" instead of including the sensitive 32-byte MLS group ID. ([#112](https://github.com/marmot-protocol/mdk/pull/112))
- Fix `admins()` to return `InvalidParameters` error when group not found, instead of incorrectly returning `NoAdmins` ([#104](https://github.com/marmot-protocol/mdk/pull/104))
crates/mdk-sqlite-storage/CHANGELOG.md (1)

28-62: Fix duplicate section headers and reposition the breaking change for pending_welcomes().

The changelog has structural issues similar to mdk-memory-storage:

  1. Duplicate section headers: ### Changed appears twice (lines 32 and 37).
  2. Misplaced breaking change: Line 35 contains a **BREAKING**: entry for pending_welcomes() (PR #110), but it's positioned under ### Changed instead of the ### Breaking changes section.
  3. Structure clarity: The ### Added section (starting line 41) mixes entries from multiple PRs (#94, #111, #110). While not incorrect, this makes it harder to scan.

Consolidate to a single set of section headers with all entries properly grouped.

🔎 Proposed fix: Consolidate sections and reposition breaking changes
## Unreleased

### Breaking changes

- **BREAKING**: Updated `messages()` implementation to accept `Option<Pagination>` parameter ([#111](https://github.com/marmot-protocol/mdk/pull/111))
- **BREAKING**: Updated `pending_welcomes()` implementation to accept `Option<Pagination>` parameter ([#110](https://github.com/marmot-protocol/mdk/pull/110))

### Changed

- Simplified validation logic to use range contains pattern for better readability ([#111](https://github.com/marmot-protocol/mdk/pull/111))
- Simplified validation logic to use range contains pattern for better readability ([#110](https://github.com/marmot-protocol/mdk/pull/110))

### Added

- Input validation for storage operations to prevent unbounded writes ([#94](https://github.com/marmot-protocol/mdk/pull/94))
  - Message content limited to 1MB
  - Group names limited to 255 bytes
  - Group descriptions limited to 2000 bytes
  - JSON fields limited to 50-100KB
  - New `Validation` error variant for validation failures
- Implemented pagination support using `Pagination` struct for group messages ([#111](https://github.com/marmot-protocol/mdk/pull/111))
- Implemented pagination support using `Pagination` struct for pending welcomes ([#110](https://github.com/marmot-protocol/mdk/pull/110))

### Fixed

- **Security (Audit Issue Z)**: Added pagination to prevent memory exhaustion from unbounded loading of group messages ([#111](https://github.com/marmot-protocol/mdk/pull/111))
- **Security (Audit Issue AA)**: Added pagination to prevent memory exhaustion from unbounded loading of pending welcomes ([#110](https://github.com/marmot-protocol/mdk/pull/110))
- **Security (Audit Issue AB)**: Added size limits to prevent disk and CPU exhaustion from unbounded user input ([#94](https://github.com/marmot-protocol/mdk/pull/94))
- Propagate `last_message_id` parse errors in `row_to_group` instead of silently converting to `None` ([#105](https://github.com/marmot-protocol/mdk/pull/105))
- **Security (Audit Issue AG)**: `all_groups` now skips corrupted rows instead of failing on the first deserialization error, improving availability when database contains malformed data ([#115](https://github.com/marmot-protocol/mdk/pull/115))
- **Security (Audit Issue AO)**: Removed MLS group identifiers from error messages to prevent metadata leakage in logs and telemetry. Error messages now use generic "Group not found" instead of including the sensitive 32-byte MLS group ID. ([#112](https://github.com/marmot-protocol/mdk/pull/112))
🤖 Fix all issues with AI Agents
In @crates/mdk-storage-traits/CHANGELOG.md:
- Around line 37-40: Remove the duplicate "Added `Pagination` struct with
`limit` and `offset` fields" entry that repeats the PR #111 note (the
near-duplicate line referencing the same `Pagination` struct appears after the
first one); delete that second `Pagination` line so only the single correct
entry for the `Pagination` struct remains and leave the other constant entries
intact, then scan for any other accidental duplicates and remove them if
present.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f2c718 and a9548ec.

📒 Files selected for processing (10)
  • crates/mdk-core/CHANGELOG.md
  • crates/mdk-core/examples/mls_memory.rs
  • crates/mdk-core/examples/mls_sqlite.rs
  • crates/mdk-memory-storage/CHANGELOG.md
  • crates/mdk-sqlite-storage/CHANGELOG.md
  • crates/mdk-sqlite-storage/src/groups.rs
  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-storage-traits/src/test_utils.rs
  • crates/mdk-uniffi/CHANGELOG.md
  • crates/mdk-uniffi/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • crates/mdk-storage-traits/src/test_utils.rs
  • crates/mdk-core/examples/mls_memory.rs
  • crates/mdk-uniffi/CHANGELOG.md
🧰 Additional context used
📓 Path-based instructions (3)
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

Never use bare URLs in markdown files; all URLs must be properly formatted using [link text](url), <url>, or ([#123](url)) for PR/issue references

Files:

  • crates/mdk-sqlite-storage/CHANGELOG.md
  • crates/mdk-core/CHANGELOG.md
  • crates/mdk-memory-storage/CHANGELOG.md
  • crates/mdk-storage-traits/CHANGELOG.md
crates/*/CHANGELOG.md

📄 CodeRabbit inference engine (AGENTS.md)

Every crate CHANGELOG entry must include a PR reference using the format ([#123](https://github.com/marmot-protocol/mdk/pull/123))

Files:

  • crates/mdk-sqlite-storage/CHANGELOG.md
  • crates/mdk-core/CHANGELOG.md
  • crates/mdk-memory-storage/CHANGELOG.md
  • crates/mdk-storage-traits/CHANGELOG.md
**/*.rs

📄 CodeRabbit inference engine (.cursor/rules/rust.mdc)

**/*.rs: All trait bounds should be written in where clauses rather than inline with type parameters in Rust
Use Self instead of the explicit type name when referring to the type in impl blocks
Derive Debug, Clone, Copy, PartialEq, Eq, and Hash for public types when possible, in that order
Derive Default for public types when there is a reasonable default value
Always use full paths for logging with tracing::<op>!(...) instead of importing and using <op>!(...)
Prefer .to_string() or .to_owned() for &str to String conversion instead of .into() or String::from()
Order imports in the following sequence: (1) core/alloc/std, (2) external crates, (3) current sub-modules with mod declarations, (4) internal crate modules
Import the fmt module rather than using full paths when implementing traits from core::fmt or std::fmt
Use relative self:: imports for sub-modules rather than bare module names
Place all use statements at the top of the file following the specified import order, never inside functions, methods, or other blocks
Avoid if let ... { } else { } constructs; use match instead when both branches are non-empty
Use if let ... { } without an else clause when a match arm would be intentionally empty
Define sub-modules using separate files (e.g., x.rs) with mod x; declaration rather than inline mod x { .. } blocks, except for tests and benches modules
Use inline mod tests { #[cfg(test)] ... } for test modules rather than creating separate tests.rs files
Use inline mod benches { #[cfg(bench)] ... } for benchmark modules rather than creating separate benches.rs files

**/*.rs: All trait bounds in generics must be placed in where clauses, not inline
Use Self instead of the type name when possible
Derive order must be: Debug, Clone, Copy, PartialEq, Eq, Hash (in this order)
Always use tracing::warn!(...), never import and use warn!(...)
Use .to_string() or .to_owned() for string ...

Files:

  • crates/mdk-sqlite-storage/src/groups.rs
  • crates/mdk-uniffi/src/lib.rs
  • crates/mdk-core/examples/mls_sqlite.rs
🧠 Learnings (6)
📚 Learning: 2026-01-03T08:39:39.799Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T08:39:39.799Z
Learning: Every change that modifies functionality must update the CHANGELOG in the affected crate

Applied to files:

  • crates/mdk-sqlite-storage/CHANGELOG.md
  • crates/mdk-core/CHANGELOG.md
  • crates/mdk-memory-storage/CHANGELOG.md
  • crates/mdk-storage-traits/CHANGELOG.md
📚 Learning: 2026-01-03T08:39:39.799Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T08:39:39.799Z
Learning: Applies to crates/*/CHANGELOG.md : Every crate CHANGELOG entry must include a PR reference using the format `([#123](https://github.com/marmot-protocol/mdk/pull/123))`

Applied to files:

  • crates/mdk-sqlite-storage/CHANGELOG.md
  • crates/mdk-core/CHANGELOG.md
  • crates/mdk-memory-storage/CHANGELOG.md
  • crates/mdk-storage-traits/CHANGELOG.md
📚 Learning: 2026-01-01T15:51:58.505Z
Learnt from: dannym-arx
Repo: marmot-protocol/mdk PR: 98
File: crates/mdk-core/CHANGELOG.md:46-46
Timestamp: 2026-01-01T15:51:58.505Z
Learning: In CHANGELOG.md files, keep the GitHub issue references (e.g., #61) as-is; do not convert '#61' (with no space) into an MD heading '# 61'. The hash without a space is intentional for GitHub auto-linking. This guideline applies to all CHANGELOG.md files in the repo.

Applied to files:

  • crates/mdk-sqlite-storage/CHANGELOG.md
  • crates/mdk-core/CHANGELOG.md
  • crates/mdk-memory-storage/CHANGELOG.md
  • crates/mdk-storage-traits/CHANGELOG.md
📚 Learning: 2025-12-03T10:18:10.432Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: .cursor/rules/rust.mdc:0-0
Timestamp: 2025-12-03T10:18:10.432Z
Learning: Applies to **/*.rs : Place all `use` statements at the top of the file following the specified import order, never inside functions, methods, or other blocks

Applied to files:

  • crates/mdk-sqlite-storage/src/groups.rs
📚 Learning: 2026-01-03T08:39:39.799Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T08:39:39.799Z
Learning: Applies to **/*.rs : Place all `use` statements at file top, never inside functions

Applied to files:

  • crates/mdk-sqlite-storage/src/groups.rs
📚 Learning: 2025-12-12T14:58:57.710Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: .cursor/rules/mls.mdc:0-0
Timestamp: 2025-12-12T14:58:57.710Z
Learning: This repository implements the Marmot protocol, which integrates MLS with the Nostr protocol. Reference https://github.com/marmot-protocol/marmot for protocol specifications and design decisions.

Applied to files:

  • crates/mdk-core/CHANGELOG.md
🧬 Code graph analysis (2)
crates/mdk-uniffi/src/lib.rs (2)
crates/mdk-core/src/messages.rs (2)
  • get_messages (117-125)
  • test_get_messages_with_pagination (1107-1163)
crates/mdk-storage-traits/src/welcomes/mod.rs (3)
  • limit (40-42)
  • offset (45-47)
  • new (35-37)
crates/mdk-core/examples/mls_sqlite.rs (5)
crates/mdk-uniffi/src/lib.rs (1)
  • get_messages (264-284)
crates/mdk-core/src/messages.rs (1)
  • get_messages (117-125)
crates/mdk-sqlite-storage/src/groups.rs (1)
  • messages (165-210)
crates/mdk-memory-storage/src/groups.rs (1)
  • messages (54-91)
crates/mdk-storage-traits/src/groups/mod.rs (1)
  • messages (110-114)
🪛 markdownlint-cli2 (0.18.1)
crates/mdk-sqlite-storage/CHANGELOG.md

49-49: Multiple headings with the same content

(MD024, no-duplicate-heading)


53-53: Multiple headings with the same content

(MD024, no-duplicate-heading)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test Coverage
🔇 Additional comments (8)
crates/mdk-core/CHANGELOG.md (1)

26-79: LGTM!

The changelog is well-structured with proper section organization, all PR references correctly formatted, and clear descriptions of breaking changes and security improvements.

crates/mdk-core/examples/mls_sqlite.rs (1)

188-245: LGTM!

The example correctly adapts to the new paginated get_messages API by passing None to use default pagination behavior. This is appropriate for demonstration code where retrieving all messages (up to the default limit) is the expected behavior.

crates/mdk-uniffi/src/lib.rs (3)

26-28: LGTM!

Good use of import aliases to distinguish between MessagePagination and WelcomePagination types, improving code clarity.


253-284: Well-documented pagination API extension.

The implementation correctly:

  • Creates pagination only when at least one parameter is specified
  • Allows partial specification (e.g., only limit or only offset) while using defaults for the other
  • Provides clear documentation with default values

1210-1295: Comprehensive pagination test coverage.

The test validates key pagination scenarios including default behavior, explicit parameters, boundary conditions (offset beyond available), and small page sizes. Good coverage for the UniFFI layer.

crates/mdk-sqlite-storage/src/groups.rs (3)

8-18: LGTM!

Import organization follows coding guidelines: standard library first, external crates next, test-only imports with #[cfg(test)], and internal crate imports last.


165-210: Solid pagination implementation.

The implementation:

  • Applies default pagination when None is passed
  • Validates limit bounds with clear error messages
  • Pushes pagination to the database layer for efficiency (LIMIT/OFFSET in SQL)
  • Correctly returns newest messages first via ORDER BY created_at DESC

459-583: Excellent test coverage for pagination.

The test thoroughly validates:

  • Multi-page retrieval with correct boundaries
  • No overlap between pages (important for pagination correctness)
  • Correct ordering (newest first)
  • Edge cases: limit=0 error, limit>MAX error, non-existent group error
  • Large offset handling (returns empty, no error)

@github-actions
Copy link

github-actions bot commented Jan 6, 2026

✅ Coverage: 86.88% → 87.42% (+0.54%)

@mubarakcoded mubarakcoded force-pushed the 69-issue-z-unbounded-function-messages-query-enables-memory-exhaustion-denial-of-service branch from d648c69 to 90eba3d Compare January 6, 2026 19:04
@github-actions
Copy link

github-actions bot commented Jan 6, 2026

✅ Coverage: 86.88% → 87.42% (+0.54%)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/mdk-memory-storage/CHANGELOG.md (1)

26-56: Consolidate duplicate section headers in the Unreleased section.

The Unreleased section contains duplicate headers (### Changed, ### Added, ### Fixed) because entries from multiple PRs (#110 and #111) were added separately. This violates markdown best practices and triggers the markdownlint warning.

Please consolidate entries from both PRs under single section headers:

  • Merge both "### Changed" sections
  • Merge both "### Added" sections
  • Merge both "### Fixed" sections
🔎 Suggested structure
 ## Unreleased
 
 ### Breaking changes
 
 - **BREAKING**: Updated `messages()` implementation to accept `Option<Pagination>` parameter ([#111](https://github.com/marmot-protocol/mdk/pull/111))
+- **BREAKING**: Updated `pending_welcomes()` implementation to accept `Option<Pagination>` parameter ([#110](https://github.com/marmot-protocol/mdk/pull/110))
 
 ### Changed
 
 - Simplified validation logic to use range contains pattern for better readability ([#111](https://github.com/marmot-protocol/mdk/pull/111))
+- Simplified validation logic to use range contains pattern for better readability ([#110](https://github.com/marmot-protocol/mdk/pull/110))
 
 ### Added
 
 - Implemented pagination support using `Pagination` struct for group messages ([#111](https://github.com/marmot-protocol/mdk/pull/111))
+- Implemented pagination support using `Pagination` struct for pending welcomes ([#110](https://github.com/marmot-protocol/mdk/pull/110))
 
 ### Fixed
 
 - **Security (Audit Issue Z)**: Added pagination to prevent memory exhaustion from unbounded loading of group messages ([#111](https://github.com/marmot-protocol/mdk/pull/111))
-- **BREAKING**: Updated `pending_welcomes()` implementation to accept `Option<Pagination>` parameter ([#110](https://github.com/marmot-protocol/mdk/pull/110))
-
-### Changed
-
-- Simplified validation logic to use range contains pattern for better readability ([#110](https://github.com/marmot-protocol/mdk/pull/110))
-
-### Added
-
-- Implemented pagination support using `Pagination` struct for pending welcomes ([#110](https://github.com/marmot-protocol/mdk/pull/110))
-
-### Fixed
-
 - **Security (Audit Issue AA)**: Added pagination to prevent memory exhaustion from unbounded loading of pending welcomes ([#110](https://github.com/marmot-protocol/mdk/pull/110))
 - **Security (Audit Issue AO)**: Removed MLS group identifiers from error messages to prevent metadata leakage in logs and telemetry. Error messages now use generic "Group not found" instead of including the sensitive 32-byte MLS group ID. ([#112](https://github.com/marmot-protocol/mdk/pull/112))
 - Fix `admins()` to return `InvalidParameters` error when group not found, instead of incorrectly returning `NoAdmins` ([#104](https://github.com/marmot-protocol/mdk/pull/104))

Based on learnings, every change that modifies functionality must update the CHANGELOG in the affected crate.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a9548ec and 90eba3d.

📒 Files selected for processing (10)
  • crates/mdk-core/CHANGELOG.md
  • crates/mdk-core/examples/mls_memory.rs
  • crates/mdk-core/examples/mls_sqlite.rs
  • crates/mdk-memory-storage/CHANGELOG.md
  • crates/mdk-sqlite-storage/CHANGELOG.md
  • crates/mdk-sqlite-storage/src/groups.rs
  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-storage-traits/src/test_utils.rs
  • crates/mdk-uniffi/CHANGELOG.md
  • crates/mdk-uniffi/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (5)
  • crates/mdk-storage-traits/src/test_utils.rs
  • crates/mdk-sqlite-storage/CHANGELOG.md
  • crates/mdk-core/examples/mls_sqlite.rs
  • crates/mdk-core/examples/mls_memory.rs
  • crates/mdk-uniffi/CHANGELOG.md
🧰 Additional context used
📓 Path-based instructions (3)
**/*.rs

📄 CodeRabbit inference engine (.cursor/rules/rust.mdc)

**/*.rs: All trait bounds should be written in where clauses rather than inline with type parameters in Rust
Use Self instead of the explicit type name when referring to the type in impl blocks
Derive Debug, Clone, Copy, PartialEq, Eq, and Hash for public types when possible, in that order
Derive Default for public types when there is a reasonable default value
Always use full paths for logging with tracing::<op>!(...) instead of importing and using <op>!(...)
Prefer .to_string() or .to_owned() for &str to String conversion instead of .into() or String::from()
Order imports in the following sequence: (1) core/alloc/std, (2) external crates, (3) current sub-modules with mod declarations, (4) internal crate modules
Import the fmt module rather than using full paths when implementing traits from core::fmt or std::fmt
Use relative self:: imports for sub-modules rather than bare module names
Place all use statements at the top of the file following the specified import order, never inside functions, methods, or other blocks
Avoid if let ... { } else { } constructs; use match instead when both branches are non-empty
Use if let ... { } without an else clause when a match arm would be intentionally empty
Define sub-modules using separate files (e.g., x.rs) with mod x; declaration rather than inline mod x { .. } blocks, except for tests and benches modules
Use inline mod tests { #[cfg(test)] ... } for test modules rather than creating separate tests.rs files
Use inline mod benches { #[cfg(bench)] ... } for benchmark modules rather than creating separate benches.rs files

**/*.rs: All trait bounds in generics must be placed in where clauses, not inline
Use Self instead of the type name when possible
Derive order must be: Debug, Clone, Copy, PartialEq, Eq, Hash (in this order)
Always use tracing::warn!(...), never import and use warn!(...)
Use .to_string() or .to_owned() for string ...

Files:

  • crates/mdk-sqlite-storage/src/groups.rs
  • crates/mdk-uniffi/src/lib.rs
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

Never use bare URLs in markdown files; all URLs must be properly formatted using [link text](url), <url>, or ([#123](url)) for PR/issue references

Files:

  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-core/CHANGELOG.md
  • crates/mdk-memory-storage/CHANGELOG.md
crates/*/CHANGELOG.md

📄 CodeRabbit inference engine (AGENTS.md)

Every crate CHANGELOG entry must include a PR reference using the format ([#123](https://github.com/marmot-protocol/mdk/pull/123))

Files:

  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-core/CHANGELOG.md
  • crates/mdk-memory-storage/CHANGELOG.md
🧠 Learnings (6)
📚 Learning: 2025-12-03T10:18:10.432Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: .cursor/rules/rust.mdc:0-0
Timestamp: 2025-12-03T10:18:10.432Z
Learning: Applies to **/*.rs : Place all `use` statements at the top of the file following the specified import order, never inside functions, methods, or other blocks

Applied to files:

  • crates/mdk-sqlite-storage/src/groups.rs
📚 Learning: 2026-01-03T08:39:39.799Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T08:39:39.799Z
Learning: Applies to **/*.rs : Place all `use` statements at file top, never inside functions

Applied to files:

  • crates/mdk-sqlite-storage/src/groups.rs
📚 Learning: 2026-01-03T08:39:39.799Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T08:39:39.799Z
Learning: Every change that modifies functionality must update the CHANGELOG in the affected crate

Applied to files:

  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-core/CHANGELOG.md
  • crates/mdk-memory-storage/CHANGELOG.md
📚 Learning: 2026-01-03T08:39:39.799Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T08:39:39.799Z
Learning: Applies to crates/*/CHANGELOG.md : Every crate CHANGELOG entry must include a PR reference using the format `([#123](https://github.com/marmot-protocol/mdk/pull/123))`

Applied to files:

  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-core/CHANGELOG.md
  • crates/mdk-memory-storage/CHANGELOG.md
📚 Learning: 2026-01-01T15:51:58.505Z
Learnt from: dannym-arx
Repo: marmot-protocol/mdk PR: 98
File: crates/mdk-core/CHANGELOG.md:46-46
Timestamp: 2026-01-01T15:51:58.505Z
Learning: In CHANGELOG.md files, keep the GitHub issue references (e.g., #61) as-is; do not convert '#61' (with no space) into an MD heading '# 61'. The hash without a space is intentional for GitHub auto-linking. This guideline applies to all CHANGELOG.md files in the repo.

Applied to files:

  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-core/CHANGELOG.md
  • crates/mdk-memory-storage/CHANGELOG.md
📚 Learning: 2025-12-12T14:58:57.710Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: .cursor/rules/mls.mdc:0-0
Timestamp: 2025-12-12T14:58:57.710Z
Learning: This repository implements the Marmot protocol, which integrates MLS with the Nostr protocol. Reference https://github.com/marmot-protocol/marmot for protocol specifications and design decisions.

Applied to files:

  • crates/mdk-core/CHANGELOG.md
🧬 Code graph analysis (2)
crates/mdk-sqlite-storage/src/groups.rs (3)
crates/mdk-storage-traits/src/groups/mod.rs (4)
  • messages (110-114)
  • limit (44-46)
  • offset (49-51)
  • new (39-41)
crates/mdk-sqlite-storage/src/db.rs (5)
  • row_to_message (210-255)
  • from (24-26)
  • from (30-32)
  • from (66-68)
  • from (72-74)
crates/mdk-sqlite-storage/src/lib.rs (3)
  • new_in_memory (120-148)
  • from_slice (49-54)
  • new (78-112)
crates/mdk-uniffi/src/lib.rs (2)
crates/mdk-core/src/messages.rs (2)
  • get_messages (117-125)
  • test_get_messages_with_pagination (1107-1163)
crates/mdk-storage-traits/src/welcomes/mod.rs (3)
  • limit (40-42)
  • offset (45-47)
  • new (35-37)
🪛 markdownlint-cli2 (0.18.1)
crates/mdk-memory-storage/CHANGELOG.md

37-37: Multiple headings with the same content

(MD024, no-duplicate-heading)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test Coverage
🔇 Additional comments (6)
crates/mdk-core/CHANGELOG.md (1)

1-79: CHANGELOG entries are well-formatted with proper PR references.

All entries in the Unreleased section include PR references in the correct format ([#XXX](https://github.com/...)), and all URLs are properly formatted. The #111-related changes are consistently documented across Breaking changes, Added, and Fixed sections.

crates/mdk-uniffi/src/lib.rs (2)

253-284: LGTM: Clean pagination implementation for foreign bindings.

The get_messages signature extension and pagination construction logic are well-designed:

  • Sensible None-handling: (None, None) → use default pagination
  • Safe type conversions: u32 → usize is always valid
  • Clear documentation with parameter descriptions

The implementation correctly forwards pagination to the underlying MDK layer and handles the boundary cases appropriately.


1210-1295: LGTM: Comprehensive pagination test coverage.

The test validates all critical scenarios:

  • Default pagination (None, None)
  • Explicit limit and offset
  • Offset beyond available messages
  • Limit boundary validation

This provides strong evidence that the pagination feature works correctly in the UniFFI layer.

crates/mdk-sqlite-storage/src/groups.rs (3)

13-18: LGTM: Imports properly placed at file top.

The test-only imports are correctly placed at the top of the file with #[cfg(test)] guards, following the coding guideline that all use statements must be at file top.

As per coding guidelines, all imports must be at file top.


165-210: LGTM: Robust pagination implementation with proper validation.

The messages() implementation correctly:

  • Validates limit is within 1..=MAX_MESSAGE_LIMIT range
  • Checks group existence before querying (prevents leaking existence info)
  • Uses parameterized SQL queries to prevent injection
  • Orders by created_at DESC for newest-first semantics
  • Intentionally omits offset validation per test requirements

The error handling and bounds checking align well with the security goals stated in the PR objectives.


459-583: LGTM: Excellent test coverage for pagination behavior.

The test comprehensively validates:

  • Multi-page retrieval with proper ordering
  • No overlap between consecutive pages
  • Default pagination behavior (None)
  • Boundary conditions (limit 0, exceeding MAX_MESSAGE_LIMIT)
  • Error paths (non-existent group, large offset)

This thorough coverage provides high confidence in the pagination implementation's correctness.

@github-actions
Copy link

github-actions bot commented Jan 6, 2026

✅ Coverage: 86.88% → 87.42% (+0.54%)

@github-actions
Copy link

github-actions bot commented Jan 6, 2026

✅ Coverage: 86.88% → 87.42% (+0.54%)

@github-actions
Copy link

github-actions bot commented Jan 7, 2026

✅ Coverage: 86.9% → 87.44% (+0.54%)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @crates/mdk-uniffi/CHANGELOG.md:
- Line 31: Update the PR reference for the get_pending_welcomes() pagination
note: replace the incorrect "#119" with "#110" where get_pending_welcomes() is
mentioned (the two occurrences in the CHANGELOG entry referencing pagination) so
it matches the documentation in crates/mdk-core/CHANGELOG.md and
crates/mdk-sqlite-storage/CHANGELOG.md.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d2926cb and 5663df7.

📒 Files selected for processing (6)
  • crates/mdk-core/CHANGELOG.md
  • crates/mdk-memory-storage/CHANGELOG.md
  • crates/mdk-sqlite-storage/CHANGELOG.md
  • crates/mdk-sqlite-storage/src/groups.rs
  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-uniffi/CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/mdk-memory-storage/CHANGELOG.md
🧰 Additional context used
📓 Path-based instructions (3)
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

Never use bare URLs in markdown files; all URLs must be properly formatted using [link text](url), <url>, or ([#123](url)) for PR/issue references

Files:

  • crates/mdk-core/CHANGELOG.md
  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-uniffi/CHANGELOG.md
  • crates/mdk-sqlite-storage/CHANGELOG.md
crates/*/CHANGELOG.md

📄 CodeRabbit inference engine (AGENTS.md)

Every crate CHANGELOG entry must include a PR reference using the format ([#123](https://github.com/marmot-protocol/mdk/pull/123))

Files:

  • crates/mdk-core/CHANGELOG.md
  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-uniffi/CHANGELOG.md
  • crates/mdk-sqlite-storage/CHANGELOG.md
**/*.rs

📄 CodeRabbit inference engine (.cursor/rules/rust.mdc)

**/*.rs: All trait bounds should be written in where clauses rather than inline with type parameters in Rust
Use Self instead of the explicit type name when referring to the type in impl blocks
Derive Debug, Clone, Copy, PartialEq, Eq, and Hash for public types when possible, in that order
Derive Default for public types when there is a reasonable default value
Always use full paths for logging with tracing::<op>!(...) instead of importing and using <op>!(...)
Prefer .to_string() or .to_owned() for &str to String conversion instead of .into() or String::from()
Order imports in the following sequence: (1) core/alloc/std, (2) external crates, (3) current sub-modules with mod declarations, (4) internal crate modules
Import the fmt module rather than using full paths when implementing traits from core::fmt or std::fmt
Use relative self:: imports for sub-modules rather than bare module names
Place all use statements at the top of the file following the specified import order, never inside functions, methods, or other blocks
Avoid if let ... { } else { } constructs; use match instead when both branches are non-empty
Use if let ... { } without an else clause when a match arm would be intentionally empty
Define sub-modules using separate files (e.g., x.rs) with mod x; declaration rather than inline mod x { .. } blocks, except for tests and benches modules
Use inline mod tests { #[cfg(test)] ... } for test modules rather than creating separate tests.rs files
Use inline mod benches { #[cfg(bench)] ... } for benchmark modules rather than creating separate benches.rs files

**/*.rs: All trait bounds in generics must be placed in where clauses, not inline
Use Self instead of the type name when possible
Derive order must be: Debug, Clone, Copy, PartialEq, Eq, Hash (in this order)
Always use tracing::warn!(...), never import and use warn!(...)
Use .to_string() or .to_owned() for string ...

Files:

  • crates/mdk-sqlite-storage/src/groups.rs
🧠 Learnings (12)
📓 Common learnings
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T08:39:39.799Z
Learning: Applies to crates/*/CHANGELOG.md : Every crate CHANGELOG entry must include a PR reference using the format `([#123](https://github.com/marmot-protocol/mdk/pull/123))`
📚 Learning: 2026-01-03T08:39:39.799Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T08:39:39.799Z
Learning: Applies to crates/*/CHANGELOG.md : Every crate CHANGELOG entry must include a PR reference using the format `([#123](https://github.com/marmot-protocol/mdk/pull/123))`

Applied to files:

  • crates/mdk-core/CHANGELOG.md
  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-uniffi/CHANGELOG.md
  • crates/mdk-sqlite-storage/CHANGELOG.md
📚 Learning: 2026-01-03T08:39:39.799Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T08:39:39.799Z
Learning: Every change that modifies functionality must update the CHANGELOG in the affected crate

Applied to files:

  • crates/mdk-core/CHANGELOG.md
  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-uniffi/CHANGELOG.md
  • crates/mdk-sqlite-storage/CHANGELOG.md
📚 Learning: 2025-12-12T14:58:57.710Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: .cursor/rules/mls.mdc:0-0
Timestamp: 2025-12-12T14:58:57.710Z
Learning: This repository implements the Marmot protocol, which integrates MLS with the Nostr protocol. Reference https://github.com/marmot-protocol/marmot for protocol specifications and design decisions.

Applied to files:

  • crates/mdk-core/CHANGELOG.md
📚 Learning: 2026-01-01T15:51:58.505Z
Learnt from: dannym-arx
Repo: marmot-protocol/mdk PR: 98
File: crates/mdk-core/CHANGELOG.md:46-46
Timestamp: 2026-01-01T15:51:58.505Z
Learning: In CHANGELOG.md files, keep the GitHub issue references (e.g., #61) as-is; do not convert '#61' (with no space) into an MD heading '# 61'. The hash without a space is intentional for GitHub auto-linking. This guideline applies to all CHANGELOG.md files in the repo.

Applied to files:

  • crates/mdk-core/CHANGELOG.md
  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-uniffi/CHANGELOG.md
  • crates/mdk-sqlite-storage/CHANGELOG.md
📚 Learning: 2025-12-03T10:18:10.432Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: .cursor/rules/rust.mdc:0-0
Timestamp: 2025-12-03T10:18:10.432Z
Learning: Applies to **/*.rs : Place all `use` statements at the top of the file following the specified import order, never inside functions, methods, or other blocks

Applied to files:

  • crates/mdk-sqlite-storage/src/groups.rs
📚 Learning: 2026-01-03T08:39:39.799Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T08:39:39.799Z
Learning: Applies to **/*.rs : Place all `use` statements at file top, never inside functions

Applied to files:

  • crates/mdk-sqlite-storage/src/groups.rs
📚 Learning: 2025-12-03T10:18:10.432Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: .cursor/rules/rust.mdc:0-0
Timestamp: 2025-12-03T10:18:10.432Z
Learning: Applies to **/*.rs : Use inline `mod tests { #[cfg(test)] ... }` for test modules rather than creating separate `tests.rs` files

Applied to files:

  • crates/mdk-sqlite-storage/src/groups.rs
📚 Learning: 2026-01-03T08:39:39.799Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T08:39:39.799Z
Learning: Applies to **/*.rs : Define modules in separate files (`mod x;` with `x.rs`), not inline; exception: `#[cfg(test)] mod tests { }` and `#[cfg(bench)] mod benches { }` are inline

Applied to files:

  • crates/mdk-sqlite-storage/src/groups.rs
📚 Learning: 2025-12-03T10:18:10.432Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: .cursor/rules/rust.mdc:0-0
Timestamp: 2025-12-03T10:18:10.432Z
Learning: Applies to **/*.rs : Define sub-modules using separate files (e.g., `x.rs`) with `mod x;` declaration rather than inline `mod x { .. }` blocks, except for `tests` and `benches` modules

Applied to files:

  • crates/mdk-sqlite-storage/src/groups.rs
📚 Learning: 2025-12-03T10:18:10.432Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: .cursor/rules/rust.mdc:0-0
Timestamp: 2025-12-03T10:18:10.432Z
Learning: Applies to **/*.rs : Use inline `mod benches { #[cfg(bench)] ... }` for benchmark modules rather than creating separate `benches.rs` files

Applied to files:

  • crates/mdk-sqlite-storage/src/groups.rs
📚 Learning: 2025-12-03T10:18:10.432Z
Learnt from: CR
Repo: marmot-protocol/mdk PR: 0
File: .cursor/rules/rust.mdc:0-0
Timestamp: 2025-12-03T10:18:10.432Z
Learning: Applies to **/*.rs : Use relative `self::` imports for sub-modules rather than bare module names

Applied to files:

  • crates/mdk-sqlite-storage/src/groups.rs
🧬 Code graph analysis (1)
crates/mdk-sqlite-storage/src/groups.rs (4)
crates/mdk-memory-storage/src/groups.rs (1)
  • messages (54-91)
crates/mdk-storage-traits/src/groups/mod.rs (4)
  • messages (110-114)
  • limit (44-46)
  • offset (49-51)
  • new (39-41)
crates/mdk-sqlite-storage/src/db.rs (5)
  • row_to_message (210-255)
  • from (24-26)
  • from (30-32)
  • from (66-68)
  • from (72-74)
crates/mdk-sqlite-storage/src/lib.rs (3)
  • new_in_memory (120-148)
  • from_slice (49-54)
  • new (78-112)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test Coverage
🔇 Additional comments (7)
crates/mdk-core/CHANGELOG.md (1)

30-31: LGTM — PR references are correct and properly formatted.

All pagination-related entries in mdk-core use the correct PR numbers (#111 for messages, #110 for welcomes) and follow the required markdown link format. The entries clearly document the pagination feature addition across Breaking changes, Added, and Fixed sections.

Also applies to: 51-52, 71-71

crates/mdk-sqlite-storage/CHANGELOG.md (1)

30-31: LGTM — PR references are correct and consistent with other crate CHANGELOGs.

All pagination-related entries follow the required format and correctly reference PR #111 for messages pagination and PR #110 for welcomes pagination. This aligns with the references used in crates/mdk-core/CHANGELOG.md.

Also applies to: 35-36, 46-46, 51-52

crates/mdk-storage-traits/CHANGELOG.md (1)

30-31: LGTM!

The changelog entries are well-structured with proper PR references, clear categorization (Breaking changes, Added, Fixed), and the security fix is appropriately documented. Based on learnings, all entries correctly include PR references in the required format.

Also applies to: 37-38, 44-44

crates/mdk-sqlite-storage/src/groups.rs (4)

8-8: LGTM!

Import correctly adds MAX_MESSAGE_LIMIT and Pagination alongside GroupStorage from the traits crate.


158-203: LGTM! Solid pagination implementation.

The implementation correctly:

  • Validates limit against MAX_MESSAGE_LIMIT to prevent memory exhaustion (addressing the security audit issue)
  • Defaults to DEFAULT_MESSAGE_LIMIT when pagination is None
  • Verifies group existence before querying
  • Uses parameterized SQL with LIMIT/OFFSET for safe, bounded retrieval
  • Returns messages in newest-first order (ORDER BY created_at DESC)

This aligns well with the in-memory reference implementation in mdk-memory-storage.


326-329: LGTM!

Test-specific imports are correctly placed at the top of the mod tests block, keeping them scoped to test code while following the guideline to not place imports inside functions.


455-579: Excellent test coverage!

This test comprehensively validates the pagination implementation:

  • Multi-page retrieval with correct ordering (newest first)
  • No overlap between pages
  • Default pagination behavior
  • Error paths (invalid limits, non-existent group)
  • Edge cases (offset beyond data, large offset values)

The test structure makes it easy to verify the pagination logic matches the expected behavior.

@mubarakcoded mubarakcoded merged commit 51b8ec5 into master Jan 7, 2026
12 checks passed
@mubarakcoded mubarakcoded deleted the 69-issue-z-unbounded-function-messages-query-enables-memory-exhaustion-denial-of-service branch January 7, 2026 08:37
@coderabbitai coderabbitai bot mentioned this pull request Mar 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Issue Z: Unbounded Function messages Query Enables Memory Exhaustion Denial of Service

4 participants