Status: Gate 3 design record approved 2026-07-26; R0 manifest/backup/cleartext baseline verified; later controls unverified
Reviewed: 2026-07-25
Current evidence: Threat model and controls only. No application, backend, policy, key, build, penetration test, dependency scan, emulator test, or device test exists.
This document defines the MVP security boundary. It is not a certification, vulnerability disclosure policy, incident-response program, or claim that the service is production-ready.
- Another ordinary Android app cannot read app-private metadata/session/cache through supported platform access.
- Account B cannot list, read, create under, modify, delete, or operate on account A's catalog or objects.
- A guessed asset/object ID or path does not bypass account authorization.
- The client never contains privileged backend credentials or permanent public object access.
- Interrupted/repeated work converges without overwrite, duplicate visible assets, or false success.
- Local disappearance cannot cause cloud deletion; cloud deletion cannot cause device deletion.
- Session, upload capability, personal identifiers, media content, fingerprints, and paths do not enter routine logs or system backup.
- Exact-original bytes are not parsed for GPS and are never described as E2E encrypted.
- Destructive server operations are authenticated, idempotent, scoped, and recoverable after partial failure.
- Compromise/failure is surfaced as blocked/stale/error state rather than silently optimistic status.
The MVP does not protect against:
- a rooted/compromised Android OS or malicious code running inside the app process;
- an attacker using an already-unlocked device/account session;
- screenshots or screen recording by the device user/OS;
- compromise, insider access, or lawful access at the managed backend/operator;
- traffic or data after it reaches an authorized endpoint/operator;
- password reuse/phishing outside the app;
- service availability, disaster recovery, legal hold, or production SLA;
- end-to-end/zero-knowledge encryption.
Android sandbox/device protection, TLS, Supabase authorization, operator controls, and the backend provider remain trusted boundaries.
| Asset | Impact if exposed/corrupted/deleted |
|---|---|
| Device and cloud photo bytes | Severe personal privacy loss |
| Embedded EXIF/location inside exact originals | Location/privacy loss |
| Private thumbnails/previews | Personal content exposure |
| Auth access/refresh session | Account takeover/private data access |
| Resumable upload URL | Unauthorized upload continuation/capability misuse |
| Catalog metadata and capture time | Activity/content inference |
| Identity/representation fingerprints | Cross-record correlation and deletion-policy bypass |
| MediaStore mappings/URIs | Local-library inference/access attempts |
| RLS/functions/service-role secret | Cross-account or full-project compromise |
| Deletion exclusion/operation state | Silent re-upload or incomplete deletion |
| Signing key/package identity | Malicious artifact impersonation/update risk |
| Test project/accounts | Limited test-data exposure and abuse/cost risk |
- Legitimate signed-in Android user.
- Another account using a legitimate client.
- Malicious/modified client with its own or stolen user token.
- Another ordinary Android application.
- Network attacker.
- Backend/operator administrator.
- Developer/test operator.
- Dependency/supply-chain attacker.
Android UI process
| app-private API boundary
Room/session/cache ---- Android Keystore
| platform-provider boundary
MediaStore
| TLS + user JWT
Supabase Auth/Data API/Storage/Functions
| elevated server-only authority
private schema/objects/auth administration
Each boundary validates identity, ownership, state, and data shape again. UI hiding is never authorization.
| Threat | Required control | Evidence |
|---|---|---|
| Cross-account metadata read | RLS enabled before grants; user_id = auth.uid(); negative tests |
Two-account Data API matrix |
| Cross-account object list/read | Private bucket; first path segment bound to auth.uid(); no public URL |
Storage list/download negative tests |
| Client writes another user's owner ID | Server derives/validates caller identity; ignores untrusted user ID | Insert/claim negative tests |
| Object overwrite/race | Random reserved path, create-only upload, no upsert, unique claim | Concurrent claim/TUS conflict test |
| Duplicate catalog after retry | Semantic idempotency key, unique constraints, reserved/verified states | Duplicate Worker and URL-expiry tests |
| False backup success | Remote finalize plus atomic local completion required | Metadata failure injection |
| Orphan exposure | Private object, non-listable reserved state, bounded cleanup | Interrupt between upload/finalize |
| Token theft from backup/log | Keystore-backed no-backup vault; no secret logging | Backup inspection, log scan |
| Upload URL leakage | Room only, no UI/log, clear on completion/expiry/sign-out | Log/database lifecycle tests |
| Fingerprint/path privacy leak | Account RLS; random path; no log/UI/work-name use | Schema/path/log review |
| Source changes during upload | Before/after snapshot and representation hash; abort old claim | Mutation injection |
| Permission callback spoof/stale cache | Re-read Android truth on resume/result | Permission transition tests |
| Local delete triggers cloud delete | No domain transition/job exists; test negative | Local disappearance integration |
| Cloud delete triggers device delete | No MediaStore delete capability in MVP | Manifest/code path review |
| Silent re-upload after delete | Account-scoped canonical exclusion loaded before scan | Sign-out/reinstall test |
| Partial account deletion | Idempotent server operation with stage state | Injected stage failure/retry |
| Cleartext/MITM | TLS only, release cleartext disabled | Network security/artifact check |
| Malicious fixture/real data leak | Safe generated fixtures, metadata/license/reference validation | Fixture validator/review |
| Dependency compromise | Minimal dependencies, locks/checksums, advisory review, staged compile | Dependency report/review |
| Debug endpoint/key in artifact | Environment separation and artifact scanning | APK/config inspection |
- Store structured app data in internal app-private Room/database files.
- Store thumbnails/previews only in bounded app-private cache.
- Never persist a full original as an unbounded staging file.
- Exclude database, session, cache, mappings, queues, fingerprints, and fixture state from Auto Backup/device transfer.
- Clear account-scoped local data/cache on sign-out.
- Treat a corrupt database as uncertainty; require confirmation before reset and never imply remote deletion.
- Disable/bypass default backend-client persistence unless it delegates to the approved vault.
- Serialize only the minimum session material required for refresh.
- Encrypt a versioned envelope using AES-GCM with a non-exportable Android Keystore key.
- Generate a unique random nonce for every write and authenticate envelope metadata as associated data.
- Store ciphertext in
noBackupFilesDir; never external/shared storage. - Do not require biometric unlock because background work needs non-interactive refresh.
- On key invalidation, corrupt ciphertext, or version mismatch, erase local session material and require sign-in.
- Never persist the user's password.
The exact implementation receives focused cryptographic review and unit/device tests. No custom algorithm or hardcoded key is allowed.
- Request only the approved image/exact-original capabilities after contextual user action.
- Do not request all-files, video, fine/background location, camera, microphone, contacts, SMS, calls, or notification permission.
- Keep raw
Uri, cursor, ContentResolver, bitmap, and Context types behind Android gateways. - Use bounded streams; cancellation closes descriptors and network work.
- Never parse/index/log GPS from exact bytes.
- Derived cloud thumbnails remove source metadata.
Allowlisted release logs contain only coarse event/error enums and short-lived random correlation IDs. They exclude:
- filenames, paths, URIs, object keys, MediaStore IDs;
- account identifiers/email;
- fingerprints, EXIF, GPS, image bytes/thumbnails;
- access/refresh tokens, publishable configuration values, signed/resumable URLs;
- raw HTTP headers/bodies or backend exception bodies.
Debug builds follow the same media/credential rule. Network body logging is disabled.
- HTTPS/TLS only; release network security rejects cleartext.
- The client receives the project URL and publishable key through local build configuration, not source literals.
- Publishable keys are not treated as authorization; every private operation requires a user JWT and server/RLS check.
- Test and future production projects use separate keys, schemas/buckets, accounts, quotas, and operator access.
- No service-role, database password, management token, Edge Function secret, or signing key enters the client or repository.
- Enable RLS on every exposed table before grants/default privileges.
- Grant only required operations to authenticated users.
- Prefer authenticated functions that derive user identity from JWT instead of accepting a trusted owner parameter.
- Use unique account-scoped constraints for logical assets and operation idempotency.
- Normal catalog queries return verified state only.
- Exclusions and destructive operations are account-scoped and non-public.
- Function search path, role, and SECURITY INVOKER/DEFINER choice receive explicit migration review; elevated functions expose only fixed validated operations.
- One private bucket for originals/thumbnails in the MVP test design.
- Random server-issued IDs form paths under the authenticated user prefix.
- Client may create/select only within its prefix and only through required operations.
- Normal original upload is create-only; no
x-upsert. - Client DELETE/UPDATE is denied unless a later minimal policy is proven. Destructive cleanup uses authenticated server code.
- Authenticated downloads or short-lived signed access only; never permanent public objects.
- Canonical identity is computed before claim using a bounded accessible stream.
- Claim is idempotent and account-scoped; it returns existing verified state or a random reservation.
- Reservation/path/session is durable locally before upload reliance.
- Representation hash and byte count are computed during actual upload.
- Source snapshot stability is checked before finalize.
- Finalize validates JWT owner, reservation, object path, Storage presence, and byte size.
- Local Backed up follows successful remote finalize and atomic Room commit.
- Expired TUS URL reuses the semantic reservation/path; path conflict queries state rather than overwriting.
The current design does not claim a server-computed cryptographic checksum. Gate 4 records the actual provider guarantees and must update the claim if stronger verification is unavailable.
- Per-item deletion and account deletion use random operation IDs plus account-scoped idempotency keys.
- The server validates current user and target ownership on every retry.
- Per-item deletion removes representations/thumbnail/catalog and creates the canonical exclusion.
- Account deletion enumerates/removes only the authenticated account prefix/rows, records stage progress, then removes Auth identity last.
- Partial failure remains queryable and retryable.
- Operator reset is separately scoped to a disposable test project/account and refuses production identifiers.
- Apply backend object-size and per-account quota before production-like testing.
- One client upload per account limits accidental concurrency/cost.
- Auth and destructive actions require bounded retry/rate control.
- 403/RLS failure is not blindly retried; it fails closed for operator review.
- Quota failure remains visibly not backed up and resumes only after explicit revalidation.
- WorkManager/backoff prevents tight network retry loops.
- The MVP makes no availability, durability, disaster-recovery, or legal-retention guarantee beyond verified provider/project behavior.
- Use the smallest approved dependency set and official repositories.
- Pin versions in the version catalog and wrapper; add dependency groups one at a time.
- Enable dependency locking/checksum verification during Gate 4 if compatible with the scaffold.
- Review primary release notes and security advisories before upgrades and artifact handoff.
- Keep Gradle/local configuration, IDE files, keystores, env files, backend dumps, and generated credentials out of source.
- Inspect the final manifest, network config, resources, BuildConfig/generated constants, and APK strings for secrets/endpoints.
- Do not run unreviewed code-generation/install scripts with backend or signing credentials.
The proposed community supabase-kt client remains replaceable behind contracts. Direct Ktor TUS/REST is the first storage-client fallback; Firebase requires a separate owner decision.
- Disposable project and operator authority confirmed.
- RLS/storage policies reviewed before test data.
- Client contains publishable config only.
- Two non-personal accounts exist outside source control.
- Safe fixture/reference validation passes.
- Network body/credential logging disabled.
- Full two-account negative matrix passes.
- Interrupted/duplicate upload produces one verified asset.
- Reserved/orphan state is private and cleaned/reconciled.
- Session vault and backup exclusion pass.
- Manifest contains no unapproved capability.
- No unbounded media buffer/staging path exists.
- All
TESTING.mdsecurity/release checks pass or gaps are disclosed. - Dependency/advisory and artifact secret scans pass.
- Per-item/account deletion partial-failure tests pass.
- Cleartext remains disabled.
- Screenshots/fixtures/logs are privacy-reviewed.
- Signing type and distribution risk are accurately described.
- Professional security/privacy review proportional to risk.
- Production operator access controls, MFA, secret rotation, backup/disaster recovery, monitoring, alerting, incident response, retention, and deletion procedures.
- Public vulnerability-reporting/security contact.
- Current Play/platform/provider compliance review.
| Risk | Planned treatment |
|---|---|
| Canonical accessible stream may not be stable across exact grant changes | Gate 4 stop condition; redesign with owner approval |
| supabase-kt is community-maintained | Isolate; direct Ktor fallback; pin/review |
| Client-computed representation hash is not server checksum | Validate source/size/TUS/TLS; document exact evidence; investigate safe remote verification |
| Operator can access non-E2E originals | Plain disclosure and production access controls later |
| Room metadata lacks app-layer encryption | Minimize, sandbox, no backup, clear sign-out; revisit for production threat model |
| Account deletion spans Storage/Postgres/Auth | Idempotent staged server operation and failure injection |
| OEM background behavior varies | WorkManager, reconciliation, physical-device evidence, honest copy |
| Package/signing/visibility not decided | Owner gate before non-debug distribution/public release |
No public security contact or disclosure program exists because the repository/service is not released. Do not place vulnerability details, tokens, private media, or exploit data in public issues. The owner must define a private contact and response policy before public distribution.
The owner approved Gate 3 on 2026-07-26, accepting this threat boundary and required controls as implementation constraints. This does not claim the controls exist, authorize a production backend or real personal media, approve secrets/signing handling, or constitute a completed security review.