From f211e16fc0ea202964b6b377466731a9356b3bc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kyle=20=F0=9F=90=86?= Date: Thu, 16 Jul 2026 12:06:03 -0400 Subject: [PATCH 1/2] keep-nip46: GC expired timed grants on the request path --- keep-nip46/src/permissions.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/keep-nip46/src/permissions.rs b/keep-nip46/src/permissions.rs index 5b5f755c..7b627379 100644 --- a/keep-nip46/src/permissions.rs +++ b/keep-nip46/src/permissions.rs @@ -450,6 +450,12 @@ impl PermissionManager { if let Some(app) = self.apps.get_mut(pubkey) { app.last_used = Timestamp::now(); app.request_count += 1; + // GC the app's expired per-kind timed grants on its request path. + // `has_unexpired_timed_grant` / `needs_approval` only skip expired + // entries without removing them, so without this they linger until + // the app's next timed grant. Dropping an already-expired entry + // never changes an authorization decision. + app.prune_expired_kind_grants(); } } @@ -640,6 +646,35 @@ mod tests { assert!(!pm.is_connected(&pubkey)); } + #[test] + fn record_usage_gcs_expired_timed_grants() { + let mut pm = PermissionManager::new(); + let pubkey = Keys::generate().public_key(); + pm.connect(pubkey, "App".into()); + + // Inject an already-expired timed grant directly (the write path always + // sets a future expiry, so simulate a grant whose window has passed). + let past = now_unix_secs().saturating_sub(100); + pm.apps + .get_mut(&pubkey) + .unwrap() + .timed_kind_grants + .insert(Kind::from(9999u16), past); + + // The read path already treats it as expired... + assert!(pm.needs_approval(&pubkey, Kind::from(9999u16))); + // ...and the next request through record_usage garbage-collects it. + pm.record_usage(&pubkey); + assert!( + pm.apps + .get(&pubkey) + .unwrap() + .timed_kind_grants + .is_empty(), + "expired timed grant must be garbage-collected on the request path" + ); + } + #[test] fn test_connect_with_permissions() { let mut pm = PermissionManager::new(); From 6d2dbfb44fdd5caf71251ad715655148326ce235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kyle=20=F0=9F=90=86?= Date: Thu, 16 Jul 2026 12:09:19 -0400 Subject: [PATCH 2/2] keep-nip46: rustfmt the timed-grant GC test --- keep-nip46/src/permissions.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/keep-nip46/src/permissions.rs b/keep-nip46/src/permissions.rs index 7b627379..f031fb7f 100644 --- a/keep-nip46/src/permissions.rs +++ b/keep-nip46/src/permissions.rs @@ -666,11 +666,7 @@ mod tests { // ...and the next request through record_usage garbage-collects it. pm.record_usage(&pubkey); assert!( - pm.apps - .get(&pubkey) - .unwrap() - .timed_kind_grants - .is_empty(), + pm.apps.get(&pubkey).unwrap().timed_kind_grants.is_empty(), "expired timed grant must be garbage-collected on the request path" ); }