fix(security): keep system data out of Android backup, fail dev builds without a keystore#33
Merged
Merged
Conversation
…s without a keystore Backup was on for both modules and the exclusion lists barely covered anything, so a Google-held backup of the device could contain: - sheaf.db, the local cache: members, groups, front history, messages - the pending offline switch/removal queue - on the watch, wear_auth_secure.xml (the encrypted companion session), wear_auth_signal.xml, and tile_data.xml (queue, history, cached fronters) The watch rules excluded "wear_auth.xml", which is the legacy plaintext store that migration deletes. So the rules named a file that no longer exists while the live credential store shipped to the cloud. Restoring it would not even work: the store is Keystore-bound, so the ciphertext is undecryptable on the restoring device, and any restored token would have been revoked anyway. Opt out entirely: allowBackup=false on both modules, and exclude every domain from both cloud-backup and device-transfer (allowBackup=false does not disable D2D transfer on API 31+, only the cloud snapshot). Everything here is recoverable by signing in again, and the watch re-pairs from the phone. Separately, dev-release.yml quietly fell back to assembleOpenDebug when KEYSTORE_BASE64 was missing, which AGP signs with the default debug keystore. Nothing downstream distinguished it: the locate step globbed any APK, cosign signed it, and it got published under the real dev version. Fail hard instead, matching what the tagged-release workflow already does.
This was referenced Jul 14, 2026
SiteRelEnby
enabled auto-merge
July 14, 2026 14:28
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First of three PRs from the latest review. This one is the security batch.
Backup was shipping system data to Google
allowBackup="true"on both modules, with exclusion rules that covered almost nothing:sheaf.db(members, groups, front history, messages)domain="database", never excludedwidget_config.xmlwear_auth_secure.xml(encrypted companion session)wear_auth_signal.xml,tile_data.xml(switch queue, history, cached fronters)tile_config.xml,complication_config.xmlOnly
datastore/(the phone token store) was excluded.The watch rules did exclude
wear_auth.xml- but that is the legacy plaintext file thatmigrateLegacyPlaintextIfNeeded()deletes. The exclusion list was never updated when the store wasencrypted and renamed to
wear_auth_secure.xml, so the rules named a file that no longer existswhile the live credential store went to the cloud.
Restoring any of it would also be broken, not just leaky: the wear store is Keystore-bound, so
restored ciphertext cannot be decrypted on the new device, and a restored token would have been
revoked server-side anyway.
Fix: opt out entirely.
allowBackup="false"on both modules, and exclude every domain from both<cloud-backup>and<device-transfer>. The second part matters:allowBackup="false"only kills thecloud snapshot, D2D transfer still runs on API 31+ unless the extraction rules say otherwise.
Trade-off, worth being explicit about: theme, base URL and widget config no longer survive a reinstall
or a device transfer. Everything that actually matters is server-side and comes back on sign-in, and the
watch re-pairs from the phone. For an app whose local cache is a list of who is in someone system, a
Google-held copy of that is not a trade I think we want to make for the convenience of not retyping a
server URL.
Dev releases could ship debug-signed
dev-release.ymlfell through toassembleOpenDebugwhenKEYSTORE_BASE64was absent. AGP signs debugvariants with the default debug keystore, and nothing downstream could tell: the locate step globs any
APK it finds, cosign signs it, and it gets published to the
devrelease with the real dev versionCode.Now it hard-fails, the way
tagged-release.ymlalready does. Also moved the keystore cleanup to a trapso a failed build cannot leave the decoded keystore on disk.
Verification
:app:assemblePlayRelease :wear:assembleReleasegreen.behaviour beyond backup eligibility).
Follow-ups (tracked, not in this PR)
allowBackup=falseships, but existing cloud snapshots are not retroactively purged. Might be worth anote in the release blurb.