From 7fec24b1bcd407438ba510f88da9ab67648d4ea4 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Fri, 25 Sep 2026 05:11:21 -0300 Subject: [PATCH] Bunker proxy: enforce signPolicy 2 on proxy accounts --- .../greenart7c3/nostrsigner/models/Account.kt | 5 ++ .../nostrsigner/ui/AccountStateViewModel.kt | 2 +- .../greenart7c3/nostrsigner/AccountTest.kt | 56 +++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 app/src/test/java/com/greenart7c3/nostrsigner/AccountTest.kt diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/models/Account.kt b/app/src/main/java/com/greenart7c3/nostrsigner/models/Account.kt index 3dc2aca3..2ef2bcd3 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/models/Account.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/models/Account.kt @@ -69,6 +69,11 @@ class Account( val saveable = _saveable.asStateFlow() init { + // Proxy accounts always sign silently: signPolicy 2 ("always accept") matches + // the proxy path, where requests bypass per-app permissions and are forwarded + // to the remote bunker. Also self-heals accounts persisted with the older + // default of 1; no UI changes account.signPolicy after construction. + if (proxy != null && signPolicy != 2) this.signPolicy = 2 scope.launch { combine(name, picture) { _, _ -> }.drop(1).collect { _saveable.value = AccountState(this@Account) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountStateViewModel.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountStateViewModel.kt index 73e3754b..bd0b42a9 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountStateViewModel.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountStateViewModel.kt @@ -230,7 +230,7 @@ class AccountStateViewModel(npub: String?) : ViewModel() { npub = remotePubkeyHex.hexToByteArray().toNpub(), name = MutableStateFlow(bunkerName), picture = MutableStateFlow(""), - signPolicy = 1, + signPolicy = 2, didBackup = true, signer = NostrSignerInternal(localKeyPair), proxy = proxyMetadata, diff --git a/app/src/test/java/com/greenart7c3/nostrsigner/AccountTest.kt b/app/src/test/java/com/greenart7c3/nostrsigner/AccountTest.kt new file mode 100644 index 00000000..a4577300 --- /dev/null +++ b/app/src/test/java/com/greenart7c3/nostrsigner/AccountTest.kt @@ -0,0 +1,56 @@ +package com.greenart7c3.nostrsigner + +import com.greenart7c3.nostrsigner.models.Account +import com.greenart7c3.nostrsigner.models.ProxyAccountMetadata +import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray +import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair +import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test + +/** + * Proxy accounts must always run with signPolicy 2 ("always accept"): proxy + * requests bypass per-app permissions and are forwarded to the remote bunker. + * The clamp in [Account.init] also self-heals accounts that were persisted with + * the older default of 1 before this rule existed. + */ +class AccountTest { + private fun buildAccount(signPolicy: Int, proxy: ProxyAccountMetadata?): Account = Account( + signer = NostrSignerInternal(KeyPair(pubKey = "11".repeat(32).hexToByteArray())), + hexKey = "22".repeat(32), + npub = "npub1test", + name = MutableStateFlow("test"), + picture = MutableStateFlow(""), + signPolicy = signPolicy, + didBackup = true, + scope = CoroutineScope(Dispatchers.Unconfined), + proxy = proxy, + ) + + @Test + fun `proxy account forces signPolicy 2 regardless of stored value`() { + val account = buildAccount( + signPolicy = 1, + proxy = ProxyAccountMetadata( + remotePubkey = "ab".repeat(32), + relays = emptyList(), + bunkerName = "bunker", + nostrConnectSecret = "", + ), + ) + + assertTrue(account.isProxy) + assertEquals(2, account.signPolicy) + } + + @Test + fun `regular account keeps its signPolicy`() { + val account = buildAccount(signPolicy = 1, proxy = null) + + assertEquals(1, account.signPolicy) + } +}