You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Close four findings from the pentest of the released build (#119)
Tested v1.11.4 as shipped (sha256 a5e9d489…, the release asset), with
androguard and a hand-written AXML parser rather than guesswork. What the
binary says about itself drove these fixes.
The ledger was being backed up in the clear. The manifest sets no
allowBackup, so Android's default of TRUE applied, and Firestore keeps
the entire ledger in an unencrypted offline cache. Between them, an adb
backup or a device transfer carried the lot off the phone. Now
allowBackup="false" plus a data_extraction_rules.xml that excludes both
the cloud-backup and device-transfer transports.
Three rules holes, each of which the rules matrix now covers:
An unverified email was treated as an identity. authEmail() read
request.auth.token.email without checking email_verified, and it gates
invite claiming for both workspaces and the shared ledger. The client
only offers Google today, whose emails are verified — but the client is
not the boundary. Enable any other provider on the project and an invite
addressed to someone else becomes claimable by anyone who can register
that address.
members.invite was a route to promotion. Its holder could set ANY
non-owner membership's roleId, their own included, so a custom "Manager"
role with members.invite and no roles.manage could raise itself to Admin.
It may still admit and re-role others; it may no longer re-role itself.
System roles could be renamed. isOwnerRole() identifies the Owner role by
name and isSystem, and roles.manage could change both — disarming the one
guard that keeps the Owner role on the workspace owner. Permissions stay
editable; the identity does not. Minting new system roles is now the
owner's alone, since delete refuses isSystem and anyone could otherwise
leave undeletable junk.
scope.own was advisory. It gated reads only, so a role holding it
alongside a module's .manage permission could read just its own records
while editing and deleting everybody's. scopedWrite now applies the same
gate to create, update and delete on transactions, dues, debts and
contacts.
38 rules tests pass against the Firestore emulator, 9 of them new.
Reverting firestore.rules fails 6 of them, so they are load-bearing.
Claude-Session: https://claude.ai/code/session_016jkAcgzuuxTfBiAgMLWgC4
Co-authored-by: Claude <noreply@anthropic.com>