Skip to content

Fix audit log integrity bugs and CI warnings - #323

Merged
kwsantiago merged 2 commits into
mainfrom
fix/ci-review-last-2-commits
Mar 4, 2026
Merged

Fix audit log integrity bugs and CI warnings#323
kwsantiago merged 2 commits into
mainfrom
fix/ci-review-last-2-commits

Conversation

@kwsantiago

@kwsantiago kwsantiago commented Mar 4, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • Refactor

    • Improved audit and signing-audit hashing to more reliably include optional details and caller info.
    • Entry deletion now requires an explicit confirmation string for both audit types.
    • Added efficient entry-count APIs so counts can be checked without loading all entries.
    • Audit logs initialize from the last stored entry to maintain continuity after restarts.
    • Pagination and caller-filtering behavior preserved and updated to align with storage changes.
  • Bug Fix

    • Encoding fallback now logs a warning when primary encoding fails.

@kwsantiago kwsantiago self-assigned this Mar 4, 2026
@coderabbitai

coderabbitai Bot commented Mar 4, 2026

Copy link
Copy Markdown

Walkthrough

Audit and signing audit subsystems: enum discriminants added, hashing switched to presence flags and length-prefixed fields, storage traits extended with load_last_entry, entry_count, and clear_entries(confirm: String), and logs updated to initialize/limit via new storage APIs. Minor bech32 fallback logging added in lib.rs.

Changes

Cohort / File(s) Summary
Audit & Signing Core
keep-mobile/src/audit.rs
Added explicit discriminants for AuditEventType, SigningRequestType, SigningDecision. Reworked AuditEntry and SigningAuditEntry hash serialization to use presence flags and length-prefixed payloads.
Storage Traits & Logs
keep-mobile/src/audit.rs (traits & impls)
AuditStorage and SigningAuditStorage gained load_last_entry(), entry_count(), and clear_entries(confirm: String); clear_entries() without args removed. AuditLog and SigningAuditLog updated to initialize last_hash from load_last_entry(), use entry_count() for limits, and expose clear_entries(confirm: String).
Tests / Mocks
.../tests/*, .../mock_* (in same file)
Mocks and tests updated to implement new storage methods (load_last_entry, entry_count, clear_entries(confirm: String)); pagination/filter tests adjusted for caller_filter usage changes.
Misc / lib
keep-mobile/src/lib.rs
generate_animated_frames signature formatting changed and max computation adjusted; reassemble_animated_frames now logs a warning when bech32 encoding fails before falling back to JSON.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 Hopping through flags and length with flair,
I nibble bytes and tuck a caller there,
A confirmation guards the clearing key,
Last-hash remembered, counts kept faithfully,
Tiny rabbit cheers for tidy audit tea ☕️

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix audit log integrity bugs and CI warnings' directly addresses the main changes: audit log refactoring with explicit discriminants, hashing logic updates, and storage trait enhancements.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/ci-review-last-2-commits

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
keep-mobile/src/audit.rs (1)

186-199: ⚠️ Potential issue | 🟡 Minor

Move max-entry check inside the last_hash lock to avoid a race window.

At Line 186, the count check happens before mutex acquisition; concurrent callers can both pass the check and exceed MAX_AUDIT_ENTRIES. Same pattern exists in the signing path after the signature expansion.

Suggested fix
-        let entry_count = self.storage.entry_count()? as usize;
-        if entry_count >= MAX_AUDIT_ENTRIES {
-            return Err(KeepMobileError::StorageError {
-                msg: format!("Audit log full: {entry_count} entries (max {MAX_AUDIT_ENTRIES})"),
-            });
-        }
-
         let mut last_hash = self
             .last_hash
             .lock()
             .map_err(|_| KeepMobileError::StorageError {
                 msg: "Lock poisoned".into(),
             })?;
+
+        let entry_count = self.storage.entry_count()? as usize;
+        if entry_count >= MAX_AUDIT_ENTRIES {
+            return Err(KeepMobileError::StorageError {
+                msg: format!("Audit log full: {entry_count} entries (max {MAX_AUDIT_ENTRIES})"),
+            });
+        }

Mirror this change in SigningAuditLog::log_event.

Also applies to: 490-516

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@keep-mobile/src/audit.rs` around lines 186 - 199, The entry-count check
currently happens before acquiring the last_hash mutex which allows a race where
two callers both see space and then exceed MAX_AUDIT_ENTRIES; move the
entry_count = self.storage.entry_count()? as usize and the comparison against
MAX_AUDIT_ENTRIES to occur while holding the last_hash lock (i.e., after
acquiring self.last_hash.lock()), so the check-and-reserve is atomic with
respect to other loggers; apply the same change to the signing path and
duplicate the fix inside SigningAuditLog::log_event (including the check after
signature expansion) so both code paths perform the count check under the same
mutex.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@keep-mobile/src/audit.rs`:
- Around line 96-115: The verify() logic currently only recomputes hashes using
the new schema (presence flags + length prefixes) so old persisted entries will
fail; update verify() to accept both formats by (a) attempting to recompute the
hash using the new serialization and, if that fails, recomputing using the
legacy flat serialization (or detect format via a leading version/presence
marker) and (b) when writing new entries, emit a clear version byte/prefix so
future reads know which schema to use; apply the same dual-format verification
and versioned writing changes to SigningAuditEntry as well (ensure
functions/methods named verify(), SigningAuditEntry
serialization/deserialization paths, and the blocks that push presence flags +
length prefixes are updated).
- Around line 135-136: Add the missing AuditStorage interface declarations to
keep_mobile.udl so the new Rust trait methods are exported to bindings: declare
a [Trait, WithForeign] interface AuditStorage that includes the existing
store_entry and load_entries signatures plus the two new methods as UDL
signatures ([Throws=KeepMobileError] u32 entry_count(); and
[Throws=KeepMobileError] void clear_entries(string confirm);). Ensure the method
names match the Rust trait symbols entry_count() and clear_entries(confirm:
String) and that the interface is included in the file consumed by
uniffi::generate_scaffolding("src/keep_mobile.udl").

---

Outside diff comments:
In `@keep-mobile/src/audit.rs`:
- Around line 186-199: The entry-count check currently happens before acquiring
the last_hash mutex which allows a race where two callers both see space and
then exceed MAX_AUDIT_ENTRIES; move the entry_count =
self.storage.entry_count()? as usize and the comparison against
MAX_AUDIT_ENTRIES to occur while holding the last_hash lock (i.e., after
acquiring self.last_hash.lock()), so the check-and-reserve is atomic with
respect to other loggers; apply the same change to the signing path and
duplicate the fix inside SigningAuditLog::log_event (including the check after
signature expansion) so both code paths perform the count check under the same
mutex.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 10067503-091b-4c2a-8c5e-e1b5c22c0711

📥 Commits

Reviewing files that changed from the base of the PR and between e141d03 and 0fb4329.

📒 Files selected for processing (2)
  • keep-mobile/src/audit.rs
  • keep-mobile/src/lib.rs

Comment thread keep-mobile/src/audit.rs
Comment thread keep-mobile/src/audit.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
keep-mobile/src/audit.rs (1)

96-115: ⚠️ Potential issue | 🟠 Major

Preserve audit-chain compatibility across hash schema upgrades.

On Line [125] and Line [429], verification recomputes only the current hash layout. Entries persisted with the prior layout will be reported as tampered after upgrade, which breaks historical chain verification.

Proposed direction (versioned hash format + dual verification)
 #[derive(uniffi::Record, Clone, Debug, Serialize, Deserialize)]
 pub struct AuditEntry {
+    #[serde(default = "default_hash_format")]
+    pub hash_format: u8,
     pub timestamp: i64,
     ...
 }

+const HASH_FORMAT_LEGACY: u8 = 1;
+const HASH_FORMAT_V2: u8 = 2;
+fn default_hash_format() -> u8 { HASH_FORMAT_LEGACY }

 impl AuditEntry {
     pub fn finalize(mut self) -> Self {
-        self.hash = self.compute_hash().to_vec();
+        self.hash_format = HASH_FORMAT_V2;
+        self.hash = self.compute_hash_v2().to_vec();
         self
     }

     fn verify(&self, prev_hash: &[u8]) -> bool {
         ...
-        let computed = self.compute_hash();
+        let computed = match self.hash_format {
+            HASH_FORMAT_V2 => self.compute_hash_v2(),
+            HASH_FORMAT_LEGACY => self.compute_hash_legacy(),
+            _ => return false,
+        };
         ...
     }
 }

Apply the same pattern to SigningAuditEntry.

Also applies to: 120-127, 395-419, 424-431

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@keep-mobile/src/audit.rs` around lines 96 - 115, The current serialization
for audit entries (see fields used in the shown block: event_type, pubkey,
success, details) does not include a version marker, and verification code
(recompute in functions around lines 125 and 429) only attempts the current hash
layout so older-format entries appear tampered after a hash-schema upgrade; fix
by adopting a versioned hash format for SigningAuditEntry and the other audit
entry serializers (emit a single version byte at the start of the serialized
record) and update verification to perform dual verification: first attempt the
current (vX) hash verification, and if that fails, attempt the prior (vY) hash
verification path before flagging tampering. Locate and change the
serializer/deserializer/hasher implementations for SigningAuditEntry and the
corresponding verify function(s) referenced in the diff to include the version
byte and add the fallback verification branch for older schema(s).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@keep-mobile/src/audit.rs`:
- Around line 96-115: The current serialization for audit entries (see fields
used in the shown block: event_type, pubkey, success, details) does not include
a version marker, and verification code (recompute in functions around lines 125
and 429) only attempts the current hash layout so older-format entries appear
tampered after a hash-schema upgrade; fix by adopting a versioned hash format
for SigningAuditEntry and the other audit entry serializers (emit a single
version byte at the start of the serialized record) and update verification to
perform dual verification: first attempt the current (vX) hash verification, and
if that fails, attempt the prior (vY) hash verification path before flagging
tampering. Locate and change the serializer/deserializer/hasher implementations
for SigningAuditEntry and the corresponding verify function(s) referenced in the
diff to include the version byte and add the fallback verification branch for
older schema(s).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 061265dc-6a8d-4d8b-86d8-7c2abd8eacd3

📥 Commits

Reviewing files that changed from the base of the PR and between 0fb4329 and dbd113c.

📒 Files selected for processing (2)
  • keep-mobile/src/audit.rs
  • keep-mobile/src/lib.rs

@kwsantiago
kwsantiago merged commit 3bf8cfa into main Mar 4, 2026
9 checks passed
@kwsantiago
kwsantiago deleted the fix/ci-review-last-2-commits branch March 4, 2026 21:38
@coderabbitai coderabbitai Bot mentioned this pull request Apr 11, 2026
4 tasks
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.

2 participants