@@ -919,4 +919,315 @@ mod tests {
919919 let decrypted = loaded. decrypt ( recipient. secret_key ( ) ) . unwrap ( ) ;
920920 assert_eq ! ( decrypted. label, Some ( "Test" . to_string( ) ) ) ;
921921 }
922+
923+ // ═══════════════════════════════════════════════════════════════════════════
924+ // ISOLATION / LEAKAGE TESTS
925+ // These tests verify that no cross-user data leakage can occur
926+ // ═══════════════════════════════════════════════════════════════════════════
927+
928+ #[ test]
929+ fn test_isolation_list_pending_only_shows_own_entries ( ) {
930+ // Scenario: Multiple users have entries in same ShareInbox
931+ // Verify: Each user only sees their own entries via list_pending()
932+ let owner = KekKeyPair :: generate ( ) ;
933+ let user_a = KekKeyPair :: generate ( ) ;
934+ let user_b = KekKeyPair :: generate ( ) ;
935+ let user_c = KekKeyPair :: generate ( ) ;
936+ let dek = DekKey :: generate ( ) ;
937+
938+ let mut inbox = ShareInbox :: new ( ) ;
939+
940+ // Create entries for different users
941+ let token_a = ShareBuilder :: new ( & owner, user_a. public_key ( ) , & dek)
942+ . path_scope ( "/for-a/" )
943+ . build ( )
944+ . unwrap ( ) ;
945+ let envelope_a = ShareEnvelope :: new ( token_a) . with_label ( "For User A" ) ;
946+ inbox. enqueue_share ( & envelope_a, user_a. public_key ( ) ) . unwrap ( ) ;
947+
948+ let token_b1 = ShareBuilder :: new ( & owner, user_b. public_key ( ) , & dek)
949+ . path_scope ( "/for-b-1/" )
950+ . build ( )
951+ . unwrap ( ) ;
952+ let envelope_b1 = ShareEnvelope :: new ( token_b1) . with_label ( "For User B - 1" ) ;
953+ inbox. enqueue_share ( & envelope_b1, user_b. public_key ( ) ) . unwrap ( ) ;
954+
955+ let token_b2 = ShareBuilder :: new ( & owner, user_b. public_key ( ) , & dek)
956+ . path_scope ( "/for-b-2/" )
957+ . build ( )
958+ . unwrap ( ) ;
959+ let envelope_b2 = ShareEnvelope :: new ( token_b2) . with_label ( "For User B - 2" ) ;
960+ inbox. enqueue_share ( & envelope_b2, user_b. public_key ( ) ) . unwrap ( ) ;
961+
962+ // User A should only see 1 entry
963+ let pending_a = inbox. list_pending ( & user_a) ;
964+ assert_eq ! ( pending_a. len( ) , 1 , "User A should see exactly 1 entry" ) ;
965+ assert ! ( pending_a[ 0 ] . is_for_recipient( user_a. public_key( ) ) ) ;
966+
967+ // User B should see 2 entries
968+ let pending_b = inbox. list_pending ( & user_b) ;
969+ assert_eq ! ( pending_b. len( ) , 2 , "User B should see exactly 2 entries" ) ;
970+ for entry in & pending_b {
971+ assert ! ( entry. is_for_recipient( user_b. public_key( ) ) ) ;
972+ }
973+
974+ // User C should see 0 entries (no shares for them)
975+ let pending_c = inbox. list_pending ( & user_c) ;
976+ assert_eq ! ( pending_c. len( ) , 0 , "User C should see no entries" ) ;
977+ }
978+
979+ #[ test]
980+ fn test_isolation_wrong_recipient_cannot_mark_read ( ) {
981+ let owner = KekKeyPair :: generate ( ) ;
982+ let intended_recipient = KekKeyPair :: generate ( ) ;
983+ let attacker = KekKeyPair :: generate ( ) ;
984+ let dek = DekKey :: generate ( ) ;
985+
986+ let mut inbox = ShareInbox :: new ( ) ;
987+
988+ let token = ShareBuilder :: new ( & owner, intended_recipient. public_key ( ) , & dek)
989+ . build ( )
990+ . unwrap ( ) ;
991+ let envelope = ShareEnvelope :: new ( token) ;
992+ let entry = inbox. enqueue_share ( & envelope, intended_recipient. public_key ( ) ) . unwrap ( ) ;
993+ let entry_id = entry. id . clone ( ) ;
994+
995+ // Attacker tries to mark_read - should fail
996+ let result = inbox. mark_read ( & entry_id, attacker. public_key ( ) ) ;
997+ assert ! ( result. is_err( ) , "Attacker should not be able to mark_read" ) ;
998+
999+ // Entry should still be Pending
1000+ let entry = inbox. get_entry ( & entry_id) . unwrap ( ) ;
1001+ assert_eq ! ( entry. status, InboxEntryStatus :: Pending , "Status should remain Pending" ) ;
1002+
1003+ // Intended recipient can mark_read
1004+ let result = inbox. mark_read ( & entry_id, intended_recipient. public_key ( ) ) ;
1005+ assert ! ( result. is_ok( ) , "Intended recipient should be able to mark_read" ) ;
1006+ assert ! ( result. unwrap( ) , "mark_read should return true" ) ;
1007+ }
1008+
1009+ #[ test]
1010+ fn test_isolation_wrong_recipient_cannot_remove ( ) {
1011+ let owner = KekKeyPair :: generate ( ) ;
1012+ let intended_recipient = KekKeyPair :: generate ( ) ;
1013+ let attacker = KekKeyPair :: generate ( ) ;
1014+ let dek = DekKey :: generate ( ) ;
1015+
1016+ let mut inbox = ShareInbox :: new ( ) ;
1017+
1018+ let token = ShareBuilder :: new ( & owner, intended_recipient. public_key ( ) , & dek)
1019+ . build ( )
1020+ . unwrap ( ) ;
1021+ let envelope = ShareEnvelope :: new ( token) ;
1022+ let entry = inbox. enqueue_share ( & envelope, intended_recipient. public_key ( ) ) . unwrap ( ) ;
1023+ let entry_id = entry. id . clone ( ) ;
1024+
1025+ // Attacker tries to remove - should fail
1026+ let result = inbox. remove_entry ( & entry_id, attacker. public_key ( ) ) ;
1027+ assert ! ( result. is_err( ) , "Attacker should not be able to remove entry" ) ;
1028+
1029+ // Entry should still exist
1030+ assert ! ( inbox. get_entry( & entry_id) . is_some( ) , "Entry should still exist" ) ;
1031+
1032+ // Intended recipient can remove
1033+ let result = inbox. remove_entry ( & entry_id, intended_recipient. public_key ( ) ) ;
1034+ assert ! ( result. is_ok( ) , "Intended recipient should be able to remove" ) ;
1035+ assert ! ( result. unwrap( ) . is_some( ) , "remove_entry should return the entry" ) ;
1036+
1037+ // Entry should be gone
1038+ assert ! ( inbox. get_entry( & entry_id) . is_none( ) , "Entry should be removed" ) ;
1039+ }
1040+
1041+ #[ test]
1042+ fn test_isolation_wrong_recipient_cannot_accept ( ) {
1043+ let owner = KekKeyPair :: generate ( ) ;
1044+ let intended_recipient = KekKeyPair :: generate ( ) ;
1045+ let attacker = KekKeyPair :: generate ( ) ;
1046+ let dek = DekKey :: generate ( ) ;
1047+
1048+ let mut inbox = ShareInbox :: new ( ) ;
1049+
1050+ let token = ShareBuilder :: new ( & owner, intended_recipient. public_key ( ) , & dek)
1051+ . path_scope ( "/secret/" )
1052+ . build ( )
1053+ . unwrap ( ) ;
1054+ let envelope = ShareEnvelope :: new ( token) . with_label ( "Secret Data" ) ;
1055+ let entry = inbox. enqueue_share ( & envelope, intended_recipient. public_key ( ) ) . unwrap ( ) ;
1056+ let entry_id = entry. id . clone ( ) ;
1057+
1058+ // Attacker tries to accept - should fail
1059+ let result = inbox. accept_entry ( & entry_id, & attacker) ;
1060+ assert ! ( result. is_err( ) , "Attacker should not be able to accept entry" ) ;
1061+
1062+ // Entry should still be Pending (not Accepted)
1063+ let entry = inbox. get_entry ( & entry_id) . unwrap ( ) ;
1064+ assert_eq ! ( entry. status, InboxEntryStatus :: Pending , "Status should remain Pending" ) ;
1065+
1066+ // Intended recipient can accept
1067+ let result = inbox. accept_entry ( & entry_id, & intended_recipient) ;
1068+ assert ! ( result. is_ok( ) , "Intended recipient should be able to accept" ) ;
1069+ let envelope = result. unwrap ( ) ;
1070+ assert_eq ! ( envelope. label, Some ( "Secret Data" . to_string( ) ) ) ;
1071+ }
1072+
1073+ #[ test]
1074+ fn test_isolation_entry_content_encrypted_per_recipient ( ) {
1075+ // Scenario: Even if attacker gets the InboxEntry, they cannot decrypt
1076+ let owner = KekKeyPair :: generate ( ) ;
1077+ let intended_recipient = KekKeyPair :: generate ( ) ;
1078+ let attacker = KekKeyPair :: generate ( ) ;
1079+ let dek = DekKey :: generate ( ) ;
1080+
1081+ let token = ShareBuilder :: new ( & owner, intended_recipient. public_key ( ) , & dek)
1082+ . path_scope ( "/confidential/" )
1083+ . build ( )
1084+ . unwrap ( ) ;
1085+ let envelope = ShareEnvelope :: new ( token) . with_label ( "Confidential" ) ;
1086+ let entry = InboxEntry :: create ( & envelope, intended_recipient. public_key ( ) ) . unwrap ( ) ;
1087+
1088+ // Attacker cannot decrypt even with direct access to the entry
1089+ let decrypt_result = entry. decrypt ( attacker. secret_key ( ) ) ;
1090+ assert ! ( decrypt_result. is_err( ) , "Attacker should not be able to decrypt" ) ;
1091+
1092+ // Intended recipient can decrypt
1093+ let decrypt_result = entry. decrypt ( intended_recipient. secret_key ( ) ) ;
1094+ assert ! ( decrypt_result. is_ok( ) , "Intended recipient should be able to decrypt" ) ;
1095+ let decrypted = decrypt_result. unwrap ( ) ;
1096+ assert_eq ! ( decrypted. label, Some ( "Confidential" . to_string( ) ) ) ;
1097+ }
1098+
1099+ #[ test]
1100+ fn test_isolation_recipient_hash_prevents_enumeration ( ) {
1101+ // Verify that recipient_key_hash correctly identifies ownership
1102+ let user_a = KekKeyPair :: generate ( ) ;
1103+ let user_b = KekKeyPair :: generate ( ) ;
1104+
1105+ // Different users should have different inbox paths
1106+ let path_a = ShareInbox :: inbox_path_for_recipient ( user_a. public_key ( ) ) ;
1107+ let path_b = ShareInbox :: inbox_path_for_recipient ( user_b. public_key ( ) ) ;
1108+
1109+ assert_ne ! ( path_a, path_b, "Different users should have different inbox paths" ) ;
1110+
1111+ // Same user should always get same path (deterministic)
1112+ let path_a2 = ShareInbox :: inbox_path_for_recipient ( user_a. public_key ( ) ) ;
1113+ assert_eq ! ( path_a, path_a2, "Same user should get same inbox path" ) ;
1114+ }
1115+
1116+ #[ test]
1117+ fn test_isolation_multi_user_inbox_no_cross_contamination ( ) {
1118+ // Comprehensive test: multiple users, multiple shares, verify complete isolation
1119+ let owner = KekKeyPair :: generate ( ) ;
1120+ let alice = KekKeyPair :: generate ( ) ;
1121+ let bob = KekKeyPair :: generate ( ) ;
1122+ let charlie = KekKeyPair :: generate ( ) ;
1123+ let dek = DekKey :: generate ( ) ;
1124+
1125+ let mut inbox = ShareInbox :: new ( ) ;
1126+
1127+ // Create shares for Alice
1128+ let token_alice = ShareBuilder :: new ( & owner, alice. public_key ( ) , & dek)
1129+ . path_scope ( "/alice-secret/" )
1130+ . build ( )
1131+ . unwrap ( ) ;
1132+ let envelope_alice = ShareEnvelope :: new ( token_alice) . with_label ( "Alice's Secret" ) ;
1133+ let entry_alice = inbox. enqueue_share ( & envelope_alice, alice. public_key ( ) ) . unwrap ( ) ;
1134+
1135+ // Create shares for Bob
1136+ let token_bob = ShareBuilder :: new ( & owner, bob. public_key ( ) , & dek)
1137+ . path_scope ( "/bob-secret/" )
1138+ . build ( )
1139+ . unwrap ( ) ;
1140+ let envelope_bob = ShareEnvelope :: new ( token_bob) . with_label ( "Bob's Secret" ) ;
1141+ let entry_bob = inbox. enqueue_share ( & envelope_bob, bob. public_key ( ) ) . unwrap ( ) ;
1142+
1143+ // === VERIFICATION: Alice's operations ===
1144+
1145+ // Alice can see only her entry
1146+ let alice_pending = inbox. list_pending ( & alice) ;
1147+ assert_eq ! ( alice_pending. len( ) , 1 ) ;
1148+ assert_eq ! ( alice_pending[ 0 ] . id, entry_alice. id) ;
1149+
1150+ // Alice cannot operate on Bob's entry
1151+ assert ! ( inbox. mark_read( & entry_bob. id, alice. public_key( ) ) . is_err( ) ) ;
1152+ assert ! ( inbox. dismiss_entry( & entry_bob. id, alice. public_key( ) ) . is_err( ) ) ;
1153+ assert ! ( inbox. remove_entry( & entry_bob. id, alice. public_key( ) ) . is_err( ) ) ;
1154+ assert ! ( inbox. accept_entry( & entry_bob. id, & alice) . is_err( ) ) ;
1155+
1156+ // Alice can operate on her own entry
1157+ assert ! ( inbox. accept_entry( & entry_alice. id, & alice) . is_ok( ) ) ;
1158+
1159+ // === VERIFICATION: Bob's operations ===
1160+
1161+ // Bob can see only his entry
1162+ let bob_pending = inbox. list_pending ( & bob) ;
1163+ assert_eq ! ( bob_pending. len( ) , 1 ) ;
1164+ assert_eq ! ( bob_pending[ 0 ] . id, entry_bob. id) ;
1165+
1166+ // Bob cannot operate on Alice's entry (even though Alice already accepted it)
1167+ assert ! ( inbox. mark_read( & entry_alice. id, bob. public_key( ) ) . is_err( ) ) ;
1168+ assert ! ( inbox. dismiss_entry( & entry_alice. id, bob. public_key( ) ) . is_err( ) ) ;
1169+ assert ! ( inbox. remove_entry( & entry_alice. id, bob. public_key( ) ) . is_err( ) ) ;
1170+
1171+ // === VERIFICATION: Charlie (no shares) ===
1172+
1173+ // Charlie sees nothing
1174+ let charlie_pending = inbox. list_pending ( & charlie) ;
1175+ assert_eq ! ( charlie_pending. len( ) , 0 ) ;
1176+
1177+ // Charlie cannot operate on anyone's entries
1178+ assert ! ( inbox. mark_read( & entry_alice. id, charlie. public_key( ) ) . is_err( ) ) ;
1179+ assert ! ( inbox. mark_read( & entry_bob. id, charlie. public_key( ) ) . is_err( ) ) ;
1180+ assert ! ( inbox. accept_entry( & entry_bob. id, & charlie) . is_err( ) ) ;
1181+ }
1182+
1183+ #[ test]
1184+ fn test_isolation_nonexistent_entry_returns_ok_false ( ) {
1185+ // Verify that operations on non-existent entries don't leak information
1186+ // (should return Ok(false) or Ok(None), not Err)
1187+ let user = KekKeyPair :: generate ( ) ;
1188+ let mut inbox = ShareInbox :: new ( ) ;
1189+
1190+ let fake_entry_id = "nonexistent-entry-id-12345" ;
1191+
1192+ // These should all return Ok with false/None, not error
1193+ // (to prevent timing attacks or enumeration)
1194+ let mark_result = inbox. mark_read ( fake_entry_id, user. public_key ( ) ) ;
1195+ assert ! ( mark_result. is_ok( ) , "Non-existent entry should return Ok" ) ;
1196+ assert ! ( !mark_result. unwrap( ) , "Should return false for non-existent" ) ;
1197+
1198+ let dismiss_result = inbox. dismiss_entry ( fake_entry_id, user. public_key ( ) ) ;
1199+ assert ! ( dismiss_result. is_ok( ) , "Non-existent entry should return Ok" ) ;
1200+ assert ! ( !dismiss_result. unwrap( ) , "Should return false for non-existent" ) ;
1201+
1202+ let remove_result = inbox. remove_entry ( fake_entry_id, user. public_key ( ) ) ;
1203+ assert ! ( remove_result. is_ok( ) , "Non-existent entry should return Ok" ) ;
1204+ assert ! ( remove_result. unwrap( ) . is_none( ) , "Should return None for non-existent" ) ;
1205+ }
1206+
1207+ #[ test]
1208+ #[ allow( deprecated) ]
1209+ fn test_isolation_list_all_deprecation_warning ( ) {
1210+ // This test exists to document that list_all() is deprecated
1211+ // and to verify it still works (for migration purposes)
1212+ let owner = KekKeyPair :: generate ( ) ;
1213+ let user = KekKeyPair :: generate ( ) ;
1214+ let dek = DekKey :: generate ( ) ;
1215+
1216+ let mut inbox = ShareInbox :: new ( ) ;
1217+
1218+ let token = ShareBuilder :: new ( & owner, user. public_key ( ) , & dek)
1219+ . build ( )
1220+ . unwrap ( ) ;
1221+ let envelope = ShareEnvelope :: new ( token) ;
1222+ inbox. enqueue_share ( & envelope, user. public_key ( ) ) . unwrap ( ) ;
1223+
1224+ // list_all() still works but is deprecated
1225+ // Prefer list_pending() with recipient key
1226+ let all = inbox. list_all ( ) ;
1227+ assert_eq ! ( all. len( ) , 1 ) ;
1228+
1229+ // Recommended alternative:
1230+ let pending = inbox. list_pending ( & user) ;
1231+ assert_eq ! ( pending. len( ) , 1 ) ;
1232+ }
9221233}
0 commit comments