Standalone backlog (split out of #319, closed as interop-complete). This is a refinement / tech-debt item, not a client-interop gap.
Problem
The Rust core is now heavily unit-tested, but the Android (Kotlin) signer layer is thin — ~2 unit + ~7 instrumented tests across ~100 files. For a key-holding app, the request-handling / permission-resolution / approval / rate-limit wiring on the Android side is under-tested (happy-path dominant).
Proposed work
Add focused tests on the security-relevant Android paths:
Nip55ContentProvider / Nip55Activity request parsing + result mapping (incl. error/reject/timeout paths).
- Permission decision + duration mapping round-trips (Kotlin <-> Rust enum mapping).
- Approval flow state (biometric failure, cancellation, kill-switch active).
- Rate-limit / velocity wiring (the delegation to the Rust limiters).
- Bunker request gating (
handleApprovalRequest authz + rate-limit + concurrency cap).
Batch / multi-event signing (added by #320 / PR #325)
The batch path is brand-new and security-sensitive; it currently has no instrumented coverage:
- TOCTOU consent-bypass regression test (highest priority): assert the signed/rejected set equals the set rendered on the approval screen. Specifically: accumulate N requests, capture the displayed snapshot, then deliver a late
onNewIntent and confirm (a) once a decision is committed (decisionLocked) further intents are ignored, and (b) handleApproveBatch/handleRejectBatch act on the displayed snapshot, never the live pending list. This guards the HIGH finding fixed in 9a9bf6c.
- Same-caller accumulation invariant: a different-caller or
get_public_key intent resets the batch; cross-caller mixing never happens.
MAX_BATCH_SIZE (20) cap: the 21st request is dropped, not accumulated.
- One-biometric-for-N semantics: a single presence gate covers the batch; per-request preApprove + grant + audit still fire for every event (parity with the single path).
- Result serialization round-trip:
serializeBatchResults output parses back to the expected {id, package, signature, result, rejected?} per request, including the batch-reject (rejected: true) case.
Acceptance
- Meaningful coverage of the unhappy paths that gate key use, not just happy-path.
- A regression test that fails if the batch signed set can diverge from the displayed set.
Standalone backlog (split out of #319, closed as interop-complete). This is a refinement / tech-debt item, not a client-interop gap.
Problem
The Rust core is now heavily unit-tested, but the Android (Kotlin) signer layer is thin — ~2 unit + ~7 instrumented tests across ~100 files. For a key-holding app, the request-handling / permission-resolution / approval / rate-limit wiring on the Android side is under-tested (happy-path dominant).
Proposed work
Add focused tests on the security-relevant Android paths:
Nip55ContentProvider/Nip55Activityrequest parsing + result mapping (incl. error/reject/timeout paths).handleApprovalRequestauthz + rate-limit + concurrency cap).Batch / multi-event signing (added by #320 / PR #325)
The batch path is brand-new and security-sensitive; it currently has no instrumented coverage:
onNewIntentand confirm (a) once a decision is committed (decisionLocked) further intents are ignored, and (b)handleApproveBatch/handleRejectBatchact on thedisplayedsnapshot, never the livependinglist. This guards the HIGH finding fixed in9a9bf6c.get_public_keyintent resets the batch; cross-caller mixing never happens.MAX_BATCH_SIZE(20) cap: the 21st request is dropped, not accumulated.serializeBatchResultsoutput parses back to the expected{id, package, signature, result, rejected?}per request, including the batch-reject (rejected: true) case.Acceptance