Merge 0394e5cdc7c1fc3d93a66470ad379361afbb4b55 into c127eb83ab94c069c32d37530d2faecd381cd2a8

This commit is contained in:
Taksh Kothari 2026-09-14 09:43:25 +05:30 committed by GitHub
commit 17d5a81c98
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 146 additions and 6 deletions

View File

@ -1,5 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Auto Backup rules for API 30 and below. android:allowBackup="false" already disables backup
on these releases, so this file is a second line of defence rather than the primary one — but
it must not disagree with data_extraction_rules.xml about what is sensitive.
Deny-by-default for the same reason: the previous list named "bitchat_crypto.xml", which
EncryptionService stopped using when it moved to "bitchat_crypto_secure".
-->
<full-backup-content>
<exclude domain="sharedpref" path="bitchat_prefs.xml"/>
<exclude domain="sharedpref" path="bitchat_crypto.xml"/>
<exclude domain="root" path="." />
<exclude domain="file" path="." />
<exclude domain="database" path="." />
<exclude domain="sharedpref" path="." />
<exclude domain="external" path="." />
<exclude domain="device_root" path="." />
<exclude domain="device_file" path="." />
<exclude domain="device_database" path="." />
<exclude domain="device_sharedpref" path="." />
</full-backup-content>

View File

@ -1,11 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
None of bitchat's app-private storage may leave the device.
These rules are deny-by-default on purpose. An allow-by-default list that names individual
files goes stale the moment storage is renamed or added, which is how "bitchat_crypto.xml"
came to be excluded here after EncryptionService had already moved to
"bitchat_crypto_secure", leaving the live key store, "bitchat_identity", and
"private_conversations.db" eligible.
android:allowBackup="false" covers cloud backup, but for apps targeting Android 12 (API 31)
or higher the platform documents that device-to-device migration cannot be disabled on some
manufacturers' devices, so <device-transfer> is the only thing between a phone migration and
a copy of the identity keys and every private message.
-->
<data-extraction-rules>
<cloud-backup>
<exclude domain="sharedpref" path="bitchat_prefs.xml"/>
<exclude domain="sharedpref" path="bitchat_crypto.xml"/>
<exclude domain="root" path="." />
<exclude domain="file" path="." />
<exclude domain="database" path="." />
<exclude domain="sharedpref" path="." />
<exclude domain="external" path="." />
<exclude domain="device_root" path="." />
<exclude domain="device_file" path="." />
<exclude domain="device_database" path="." />
<exclude domain="device_sharedpref" path="." />
</cloud-backup>
<device-transfer>
<exclude domain="sharedpref" path="bitchat_prefs.xml"/>
<exclude domain="sharedpref" path="bitchat_crypto.xml"/>
<exclude domain="root" path="." />
<exclude domain="file" path="." />
<exclude domain="database" path="." />
<exclude domain="sharedpref" path="." />
<exclude domain="external" path="." />
<exclude domain="device_root" path="." />
<exclude domain="device_file" path="." />
<exclude domain="device_database" path="." />
<exclude domain="device_sharedpref" path="." />
</device-transfer>
</data-extraction-rules>

View File

@ -0,0 +1,97 @@
package com.bitchat.android
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import java.io.File
/**
* bitchat's app-private storage — the Noise/Nostr key material, the identity store and the
* private conversation database — must never be handed to Auto Backup or to a device-to-device
* transfer.
*
* These rules used to be an allow-by-default list naming two files, and it went stale: it
* excluded "bitchat_crypto" after EncryptionService had already moved to
* "bitchat_crypto_secure". So the invariant pinned here is deny-by-default over whole domains,
* which cannot go stale when storage is renamed or added.
*/
class BackupExclusionContractTest {
private val resourcesDirectory = File("src/main/res")
private val manifest = File("src/main/AndroidManifest.xml")
private val allDomains = setOf(
"root",
"file",
"database",
"sharedpref",
"external",
"device_root",
"device_file",
"device_database",
"device_sharedpref"
)
private fun rulesFile(name: String) = File(resourcesDirectory, "xml/$name").readText()
/** Domains excluded at path "." within [section], or across the file when it has no sections. */
private fun excludedDomains(xml: String, section: String?): Set<String> {
val scope = if (section == null) {
xml
} else {
Regex("""<$section[^>]*>(.*?)</$section>""", RegexOption.DOT_MATCHES_ALL)
.find(xml)
?.groupValues
?.get(1)
?: fail("missing <$section> section")
}
return Regex("""<exclude\s+domain="([^"]+)"\s+path="\."\s*/>""")
.findAll(scope)
.map { it.groupValues[1] }
.toSet()
}
private fun fail(message: String): Nothing = throw AssertionError(message)
@Test
fun `cloud backup excludes every storage domain`() {
assertEquals(
allDomains,
excludedDomains(rulesFile("data_extraction_rules.xml"), "cloud-backup")
)
}
@Test
fun `device to device transfer excludes every storage domain`() {
// allowBackup=false does not reliably disable D2D migration on Android 12+, so this
// section is the only thing protecting a phone migration.
assertEquals(
allDomains,
excludedDomains(rulesFile("data_extraction_rules.xml"), "device-transfer")
)
}
@Test
fun `legacy auto backup rules exclude every storage domain`() {
assertEquals(allDomains, excludedDomains(rulesFile("backup_rules.xml"), null))
}
@Test
fun `no rule opts any path back in`() {
for (name in listOf("data_extraction_rules.xml", "backup_rules.xml")) {
assertFalse(
"$name must not re-include anything",
rulesFile(name).contains("<include")
)
}
}
@Test
fun `the manifest still disables backup and points at both rule files`() {
val text = manifest.readText()
assertTrue(text.contains("""android:allowBackup="false""""))
assertTrue(text.contains("""android:dataExtractionRules="@xml/data_extraction_rules""""))
assertTrue(text.contains("""android:fullBackupContent="@xml/backup_rules""""))
}
}