Firebase Storage security rules explained
Cloud Storage for Firebase holds the files your users upload: profile photos, documents, attachments. Like Firestore and Realtime Database, it's protected by Security Rules. If those rules are too loose, anyone with your project ID can list, download, overwrite or delete files.
This guide covers how Storage rules work, the rules most apps actually need, and how to check your live bucket.
How Storage rules are structured
Storage rules live in storage.rules and look like this:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /some/path/{fileName} {
allow read, write: if <condition>;
}
}
}match /b/{bucket}/omatches objects in your bucket. Your file paths go inside it.{fileName}captures one path segment.{allPaths=**}matches everything below a path (requiresrules_version = '2').- Anything not explicitly allowed is denied.
read and write can be split into finer operations:
| Operation | Covers |
|---|---|
get | Downloading a single file or its metadata |
list | Listing the files under a path |
create | Uploading a new file |
update | Overwriting an existing file or its metadata |
delete | Deleting a file |
Splitting them is how you allow "anyone can view this image" without also allowing "anyone can list every file in the folder".
Useful variables
| Variable | What it is |
|---|---|
request.auth | The signed-in user (null if not signed in). request.auth.uid is their user ID |
request.resource | The file being uploaded: .size, .contentType, .metadata |
resource | The file that already exists (for reads, updates and deletes) |
The rules to avoid
Wide open:
match /{allPaths=**} {
allow read, write: if true;
}Anyone can download, upload, overwrite and delete anything. Never use this outside the emulator.
Test mode left on:
match /{allPaths=**} {
allow read, write: if request.time < timestamp.date(2026, 12, 1);
}Test-mode rules are wide open until the date passes. Projects often launch before that date, and some teams "fix" the expiry by pushing the date further out.
Signed in is not the same as allowed:
match /{allPaths=**} {
allow read, write: if request.auth != null;
}If anyone can sign up for your app (or use anonymous auth), anyone can read and overwrite every user's files. See is a Firebase API key a secret? for why sign-up is open to people outside your app.
Rules most apps need
Private files per user
Each user can only read and write files under their own folder, e.g. users/{uid}/...:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /users/{userId}/{allPaths=**} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
}
}Upload files to paths that include the user's UID, e.g. users/${user.uid}/avatar.jpg.
Public images, private uploads
Anyone can view a profile picture by its exact path, but nobody can list the folder, and only the owner can upload:
match /avatars/{userId}/{fileName} {
allow get: if true;
allow list: if false;
allow create, update: if request.auth != null && request.auth.uid == userId;
allow delete: if request.auth != null && request.auth.uid == userId;
}Limit upload size and type
Without limits, any signed-in user can upload huge or unexpected files and run up your storage bill:
match /users/{userId}/photos/{fileName} {
allow read: if request.auth != null && request.auth.uid == userId;
allow create, update: if request.auth != null
&& request.auth.uid == userId
&& request.resource.size < 5 * 1024 * 1024
&& request.resource.contentType.matches('image/.*');
allow delete: if request.auth != null && request.auth.uid == userId;
}This allows images under 5 MB only. Note that contentType is set by the client, so treat it as a guard against mistakes, not proof of what the file contains. Validate files server-side (e.g. in a Cloud Function) if that matters.
Check data in Firestore (cross-service rules)
Storage rules can read Firestore documents, for example to let only members of a team access the team's files:
match /teams/{teamId}/{allPaths=**} {
allow read, write: if request.auth != null
&& firestore.exists(/databases/(default)/documents/teams/$(teamId)/members/$(request.auth.uid));
}Cross-service rules have to be enabled for your project the first time you deploy them, and each lookup counts as a Firestore read.
Common mistakes
- Allowing
listby accident.allow readincludeslist. If file names are guessable or contain personal information, listing exposes them. Usegetwhen users only need to download a file they already know about. - Rules on folders that don't match the real paths. If your app uploads to
uploads/{uid}/...but your rules protectusers/{uid}/..., the upload either fails or falls through to a broader rule. Check the paths in your client code. - A broad catch-all rule. In Storage, if any matching rule allows a request, it's allowed. One
match /{allPaths=**} { allow read: if true; }at the bottom overrides all your careful rules above it. - Forgetting other buckets. If your project uses more than one bucket, every bucket needs rules.
How to test your Storage rules
- Unit-test rules in the emulator. Use the Firebase Local Emulator Suite with
@firebase/rules-unit-testingto assert that, for example, user A can't read user B's files. It's the best way to catch regressions before you deploy. - Check the live bucket from the outside. The emulator tests your rules file, not necessarily what's deployed. FireScan tests the real project:
firescan > set projectID your-project-id
firescan > set apiKey AIza...
firescan > scan --unauth # can anyone list or download files with no login?
firescan > auth --create-account
firescan > scan --storage # what can a signed-in stranger list and read?To check whether strangers can upload, use test mode, which writes clearly marked test files and asks for confirmation first:
firescan > scan --write --storage --testOnly scan projects you own or have permission to test, and use test mode on staging unless you've agreed otherwise.
Checklist
- [ ] No
if trueor test-mode date rules in production - [ ] User files live under a path containing the UID, with matching rules
- [ ]
listis only allowed where it's needed - [ ] Upload size and content type are limited
- [ ] No catch-all rule that overrides specific ones
- [ ] Every bucket in the project has rules
- [ ] Rules are covered by emulator tests, and the live bucket has been scanned
