Real-Time Chat App
A query filter is a convenience, not a security boundary — nothing stops an attacker sending the unfiltered read instead.
- Flutter
- Dart
- Firebase
About this project
A real-time one-to-one chat app in Flutter — email sign-up, a room list, live message streams, and read state — backed by Firebase Auth and Cloud Firestore.
The app's own UI only ever asks Firestore for messages in one room at a time, filtered by roomId. That filter makes the app work, and it makes the naive developer confident: they test the shape their UI sends, see the right messages come back, and ship. But a filter in a query is a convenience for the client, not a rule the database enforces — an attacker does not have to use the app at all. They send the unfiltered read, and under a naive ruleset every private message in the entire collection comes back. This project proves it: three real Auth-emulator users, one private room, and an outsider who reads both participants' messages under the naive rules and is blocked by HTTP 403 under the fixed ones — while the real participant's own scoped read still works. The fix checks room membership against the stored room document, which the client cannot forge.
What you will learn
- Understand why a client-side query filter can never be a security control
- Write Firestore rules that check room membership on the stored document, not the request
- Prove a rules fix by sending the same unfiltered read an attacker would, not just the app's own query
- Structure a Flutter app behind service interfaces so its tests need no real Firebase
- Apply the same "filter is not a boundary" lesson to any list-scoped API endpoint
Before you start
- Flutter and Dart basics — widgets, state, and streams
- Flutter SDK 3.x — no paid Firebase project needed, the local emulators do everything
- Firebase CLI for the security-rules proof
Academic integrity notice
These projects are provided for learning, experimentation and reference. Understand the code, modify it for your own requirements, and acknowledge external work according to your institution’s academic policies. Priodemy does not supply work to be submitted as your own.
Features
- Two real rulesets and one Dart proof script that runs the identical reads against both
- A real outsider account reading private messages under naive rules, blocked 403 under fixed ones
- Both the app's own scoped query and the unfiltered read an attacker would actually send
- Membership checked against the stored room document, not against anything the request claims
- 38 tests including full widget-tree flows, running against fakes with no real Firebase
- Builds for web and Windows desktop, both verified with real builds
Technology stack
Track: Mobile · Level: Major project
What is included
- A complete Flutter chat app — auth, room list, live message stream — commented throughout
- firestore.rules and firestore.naive.rules, plus tool/verify_security_rules.dart, the real proof
- A README with both full emulator transcripts and the exact commands that produced them
Delivered as a 112 KB ZIP. SHA-256 published under Changelog so you can verify it.
System requirements
- Operating system
- Any
- Runtime
- Flutter SDK 3.x, Firebase CLI. No paid Firebase project needed.
Licence
Learn from it, change it, and use it in your own coursework and personal projects. Do not resell it or pass it off as your own original work.
- Personal and educational use
- Modifying the code however you like
- Using it as the basis of your own coursework, with honest acknowledgement
- Putting your modified version in a portfolio or on your own GitHub
- Reselling or redistributing the project, modified or not
- Presenting it as your own original work
- Commercial deployment — see the Commercial licence
Support window: 6 months from download.
Frequently asked
Do I need a paid Firebase project to run this?
No. The app and both security-rules proofs run entirely against the free local Firebase emulators. lib/firebase_options.dart ships with obvious placeholder values, not a real API key.
Why is filtering by roomId in the query not enough?
Because the filter is part of the request, and the attacker writes the request. The app's UI sends a roomId-scoped query, so it looks correct in testing — but an attacker skips the app entirely and sends an unfiltered read of the messages collection. Under the naive ruleset that returns every private message in the database. Only a rule the server evaluates can stop it, which is exactly what the fixed ruleset does.
Can the tests run without Firebase?
Yes — all 38 of them. The app is built behind AuthService and ChatRepository interfaces with test fakes, so the widget tests exercise the real widget tree with no Firebase anywhere. The security-rules proof is separate and does need the emulators, since the whole point is testing what the real Firestore rules engine does.
Changelog
- First release
SHA-25635ae74940bd95f83ca8f04315100893f654fe906d7496e6c4e87232f685cdc43