From 5b3522c702ca9f7ffa44fab25068ad865473c966 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 1 Jun 2026 13:06:10 +0000 Subject: [PATCH] Honor kind on auto-handled NIP-44 v3 rejections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ContentProvider auto-handle path looked the v3 permission up by (key, type, kind) and, on miss, fell back to (key, type) — whose SQL matches any kind. A v3 reject saved with kind=A therefore leaked to a request with kind=B, auto-rejecting it. Add a dedicated DAO query for the explicit "all kinds" (kind IS NULL) grant and use that as the fallback instead, mirroring how the manual bunker and intent screens already resolve v3 permissions. --- .../com/greenart7c3/nostrsigner/SignerProvider.kt | 6 +++++- .../nostrsigner/database/ApplicationDao.kt | 12 ++++++++++++ .../nostrsigner/database/CachingApplicationDao.kt | 10 +++++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/SignerProvider.kt b/app/src/main/java/com/greenart7c3/nostrsigner/SignerProvider.kt index 1da97b10..52bacfa2 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/SignerProvider.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/SignerProvider.kt @@ -319,8 +319,12 @@ class SignerProvider : ContentProvider() { // SignerType, kind); fall back to a kind=null "all kinds" // grant. V3 grants do NOT satisfy V2 requests and vice versa. var permission = if (isV3) { + // V3 grants are kind-scoped; fall back to the explicit + // "all kinds" (kind IS NULL) grant only, never to any + // other kind — otherwise e.g. a kind-A reject would + // leak to a kind-B request. permDao.getPermission(packageName, type.toString(), v3Kind!!) - ?: permDao.getPermission(packageName, type.toString()) + ?: permDao.getPermissionAllKinds(packageName, type.toString()) } else { // Classify the content to determine EncryptedDataKind-based permission type val classifyContent = if (isEncrypt) content else (result ?: content) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/database/ApplicationDao.kt b/app/src/main/java/com/greenart7c3/nostrsigner/database/ApplicationDao.kt index 8de614b2..922dc21a 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/database/ApplicationDao.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/database/ApplicationDao.kt @@ -78,6 +78,18 @@ interface ApplicationDao { type: String, ): ApplicationPermissionsEntity? + /** + * Match an "all kinds" grant — the row whose `kind` column is `NULL`. + * Distinct from [getPermission] (no kind), which matches any row of the + * given type regardless of kind: V3's kind-scoped permission model needs + * the explicit `IS NULL` to avoid kind-A rejects leaking to kind-B requests. + */ + @Query("SELECT * FROM applicationPermission WHERE pkKey = :key AND type = :type AND kind IS NULL AND relay = '' LIMIT 1") + fun getPermissionAllKinds( + key: String, + type: String, + ): ApplicationPermissionsEntity? + @Query("SELECT * FROM applicationPermission WHERE pkKey = :key AND type = :type AND kind = :kind AND relay = :relay LIMIT 1") fun getPermissionForRelay( key: String, diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/database/CachingApplicationDao.kt b/app/src/main/java/com/greenart7c3/nostrsigner/database/CachingApplicationDao.kt index 777fd9b6..e6de66cc 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/database/CachingApplicationDao.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/database/CachingApplicationDao.kt @@ -21,7 +21,7 @@ import androidx.paging.PagingSource class CachingApplicationDao( private val delegate: ApplicationDao, ) : ApplicationDao { - private enum class Method { SIGN_POLICY, PERM, PERM_KIND, PERM_RELAY, PERM_WILDCARD } + private enum class Method { SIGN_POLICY, PERM, PERM_KIND, PERM_ALL_KINDS, PERM_RELAY, PERM_WILDCARD } private data class Key( val method: Method, @@ -81,6 +81,14 @@ class CachingApplicationDao( return result } + override fun getPermissionAllKinds(key: String, type: String): ApplicationPermissionsEntity? { + val k = Key(Method.PERM_ALL_KINDS, key, type, null, null) + lookup(k)?.let { return it.permission } + val result = delegate.getPermissionAllKinds(key, type) + store(k, Value(null, result)) + return result + } + override fun getPermissionForRelay(key: String, type: String, kind: Int, relay: String): ApplicationPermissionsEntity? { val k = Key(Method.PERM_RELAY, key, type, kind, relay) lookup(k)?.let { return it.permission }