7d9532ed3a3b

fix: Require versioned consent for site icons (v2.14.74)

Treat pre-upgrade favicon settings as unconsented, gate every icon rendering and prefetch path on the current disclosure version, and record consent only after the DuckDuckGo inventory warning is accepted. Verification: migration and consent regressions passed; 1015 JVM tests passed with zero failures/errors; lintDebug and assembleRelease passed.

time
2026-08-31 12:33 UTC · 1 month ago
machine
koh2
parent
1009fd2d3ebd
7 files VERSION+1 −1app/src/main/java/com/peachpasswords/MainActivity.kt+2 −1app/src/main/java/com/peachpasswords/autofill/AutofillEntryPickerActivity.kt+2 −1app/src/main/java/com/peachpasswords/ui/settings/SettingsCardBodies.kt+4 −2app/src/main/java/com/peachpasswords/ui/settings/SettingsViewModel.kt+4 −1app/src/main/java/com/peachpasswords/vault/VaultSettings.kt+14 −0app/src/test/java/com/peachpasswords/vault/VaultSettingsDefaultsTest.kt+21 −0
VERSION+1 −1
@@ -1 +1 @@1-2.14.731+2.14.74

view file

app/src/main/java/com/peachpasswords/MainActivity.kt+2 −1
@@ -41,6 +41,7 @@4141 import com.peachpasswords.updates.UpdateRepository4242 import com.peachpasswords.vault.VaultLockTimer4343 import com.peachpasswords.vault.VaultRepository44+import com.peachpasswords.vault.faviconsAllowed4445 4546 // FragmentActivity (not ComponentActivity) so androidx BiometricPrompt can attach.4647 @AndroidEntryPoint@@ -122,7 +123,7 @@122123             }123124             CompositionLocalProvider(124125                 LocalFavicons provides favicons,125-                LocalFaviconsEnabled provides settings.faviconsEnabled,126+                LocalFaviconsEnabled provides faviconsAllowed(settings),126127             ) {127128                 PeachTheme(128129                     accent = accent,

view file

app/src/main/java/com/peachpasswords/autofill/AutofillEntryPickerActivity.kt+2 −1
@@ -67,6 +67,7 @@6767 import com.peachpasswords.vault.VaultEntry6868 import com.peachpasswords.vault.isCard6969 import com.peachpasswords.vault.VaultRepository70+import com.peachpasswords.vault.faviconsAllowed7071 import dagger.hilt.android.AndroidEntryPoint7172 import kotlinx.coroutines.delay7273 import kotlinx.coroutines.launch@@ -154,7 +155,7 @@154155         setContent {155156             CompositionLocalProvider(156157                 LocalFavicons provides favicons,157-                LocalFaviconsEnabled provides settings.faviconsEnabled,158+                LocalFaviconsEnabled provides faviconsAllowed(settings),158159             ) {159160             PeachTheme(accent = accent, textSize = settings.textSize) {160161                 val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)

view file

app/src/main/java/com/peachpasswords/ui/settings/SettingsCardBodies.kt+4 −2
@@ -78,6 +78,7 @@7878 import androidx.hilt.navigation.compose.hiltViewModel7979 import com.peachpasswords.vault.VaultSettings8080 import com.peachpasswords.vault.VaultSnapshot81+import com.peachpasswords.vault.faviconsAllowed8182 import kotlinx.coroutines.launch8283 import kotlinx.coroutines.delay8384 import java.text.SimpleDateFormat@@ -349,8 +350,9 @@349350     SubLabel("ICONS", fg)350351     RowLine(fg) {351352         Name("Site icons", fg)352-        SubtleButton(if (s.faviconsEnabled) "On" else "Off", fg) {353-            if (s.faviconsEnabled) vm.setFaviconsEnabled(false) else showFaviconDisclosure = true353+        val siteIconsAllowed = faviconsAllowed(s)354+        SubtleButton(if (siteIconsAllowed) "On" else "Off", fg) {355+            if (siteIconsAllowed) vm.setFaviconsEnabled(false) else showFaviconDisclosure = true354356         }355357     }356358     if (showFaviconDisclosure) {

view file

app/src/main/java/com/peachpasswords/ui/settings/SettingsViewModel.kt+4 −1
@@ -286,7 +286,10 @@286286     fun setReduceMotion(on: Boolean) = mutate { it.copy(reduceMotion = on) }287287     fun setTextSize(size: String) = mutate { it.copy(textSize = size) }288288     fun setUiScale(scale: String) = mutate { it.copy(uiScale = scale) }289-    fun setFaviconsEnabled(on: Boolean) = mutate { it.copy(faviconsEnabled = on) }289+    fun setFaviconsEnabled(on: Boolean) = mutate {290+        if (on) com.peachpasswords.vault.grantCurrentFaviconConsent(it)291+        else it.copy(faviconsEnabled = false)292+    }290293     fun setBreachMonitoring(on: Boolean) = mutate { it.copy(breachMonitoring = on) }291294     fun setBiometrics(on: Boolean) = mutate { it.copy(biometricsEnabled = on) }292295     fun setPasskeyProviderEnabled(on: Boolean) = mutate { it.copy(passkeyProviderEnabled = on) }

view file

app/src/main/java/com/peachpasswords/vault/VaultSettings.kt+14 −0
@@ -14,6 +14,8 @@1414     val uiScale: String = "auto",1515     /** U1 site icons in lists; off (default) keeps the cleaner initials-only look. */1616     val faviconsEnabled: Boolean = false,17+    /** Version of the DuckDuckGo domain-inventory disclosure the user accepted. */18+    val faviconConsentVersion: Int = 0,1719     val biometricsEnabled: Boolean = false,1820     val trashRetentionDays: Int? = null,1921     val sortOrder: String = "url-first",@@ -27,6 +29,18 @@2729     val duressConfigured: Boolean = false,2830     val shortcuts: Map<String, ShortcutBinding> = emptyMap(),2931     val allowScreenshots: Boolean = false,32+)33+34+internal const val CURRENT_FAVICON_CONSENT_VERSION = 135+36+/** Network-backed site icons are enabled only after consent to the current disclosure. */37+fun faviconsAllowed(settings: VaultSettings): Boolean =38+    settings.faviconsEnabled && settings.faviconConsentVersion >= CURRENT_FAVICON_CONSENT_VERSION39+40+/** Record an explicit acceptance of the current third-party favicon disclosure. */41+fun grantCurrentFaviconConsent(settings: VaultSettings): VaultSettings = settings.copy(42+    faviconsEnabled = true,43+    faviconConsentVersion = CURRENT_FAVICON_CONSENT_VERSION,3044 )3145 3246 @Serializable

view file

app/src/test/java/com/peachpasswords/vault/VaultSettingsDefaultsTest.kt+21 −0
@@ -35,4 +35,25 @@3535         val decoded = VaultCodec.decodeSettings(encoded)3636         assertEquals(original, decoded)3737     }38+39+    @Test fun legacy_enabled_favicons_doNotGainConsentByUpgrade() {40+        val legacy = VaultCodec.decodeSettings("{\"faviconsEnabled\":true}")!!41+42+        assertEquals(0, legacy.faviconConsentVersion)43+        assertFalse(faviconsAllowed(legacy))44+    }45+46+    @Test fun currentExplicitConsentAllowsEnabledFavicons() {47+        val consented = grantCurrentFaviconConsent(VaultSettings())48+49+        assertTrue(consented.faviconsEnabled)50+        assertEquals(CURRENT_FAVICON_CONSENT_VERSION, consented.faviconConsentVersion)51+        assertTrue(faviconsAllowed(consented))52+    }53+54+    @Test fun disabledFaviconsStayOffAfterPriorConsent() {55+        val disabled = grantCurrentFaviconConsent(VaultSettings()).copy(faviconsEnabled = false)56+57+        assertFalse(faviconsAllowed(disabled))58+    }3859 }

view file