Coordinated disclosure, resolved and rewarded
The Firebase behind the mirror
A supermarket photo booth on a city-centre market square turned faces into shareable AR clips. The link it gave back led to the booth's whole database, and that database answered anyone, with no login.
Colin de Vries··8 min read
One HTTP request, sent without a login, returned every campaign document in the project. The project's security rules allowed it.
# no token, no cookie, no key beyond the public client key in the page bundle
GET /v1/projects/<studio-project>/databases/(default)/documents
// the effect of the rules, as seen from outside
match /databases/{database}/documents {
match /{document=**} { allow read: if true; }
}
The project was a single Firebase backend run by a European AR studio that builds photo experiences for events and brand activations. On 2 June 2026 anyone could read all of it: names, email addresses, consent flags, and links to 1,500 photos taken at AR booths, from seven marketing campaigns. The brands behind them include a national supermarket, a mobile bank and a live-events venue brand.
I reported it the same day to the AR studio and the three brands. The AR studio shipped a partial fix on 3 June and confirmed full remediation on 17 June. After that, the same request returned HTTP 403 PERMISSION_DENIED. This write-up leaves out the company names and keeps the technical detail.
From the market square to the fix
- 01 JuneveningA share link at the market squareHTTP 200 · read confirmed
I tried the supermarket's AR board on a city-centre market square. I scanned the QR code and made a clip. The share link pointed at a
*.vercel.appsubdomain instead of a brand domain. In DevTools, the JavaScript bundle held the Firebase config with the project id and the public API key. Every Firebase web app ships that, so I tested the security rules next. Firestore answered a read with no login. - 02 Jun09:08 UTCReported to four parties
I stopped at proof of access and sent the report the same morning. The AR studio got it by direct email, as the processor. The supermarket, the bank and the venue brand got it as the data controllers.
- 02 Jun11:37 UTCTriaged and accepted
The platform that runs the supermarket's and the bank's disclosure programs accepted the report. I had rated it Critical. Their triage set it to High, CVSS 7.5, because the exposure was read-only. I agree with that score.
- 03 Junre-testA partial fixHTTP 200 · 2 of 3 mirrors still open
The AR studio reported the issue fixed. On re-test, the other clients' campaigns returned permission-denied. Two of the three supermarket mirrors were still open and still served participants' photos. The mirror screen had told those participants that their photos were not stored.
- 17 JunconfirmedClosedHTTP 403 · access closed
The AR studio confirmed full remediation, and every unauthenticated request now returned permission-denied. The supermarket later resolved its report and ended the campaign. The bank resolved its report as informational, because the data came from an offline event and not from its own customers.
How a photo booth gives up a database
Firebase suits this kind of app. A pop-up booth needs a backend that nobody has to run, and Firestore with Storage is that. The client talks to Google directly, so the app ships with a public API key in its JavaScript. That is by design, and it is not the flaw.
The security rules decide whether only the app can read its data, or the whole internet can. I cannot see how this project's rules came to be. The shortest rule that makes a booth work is allow read: if true, and the rules here behaved exactly like it. Nobody tightened them during a campaign built to a deadline.
From the booth's share link I had the project id and the key. One documented Firestore endpoint lists the collections, and a second one reads them. Every call returned HTTP 200. It took no password guessing and no exploit, only requests that the rules should have refused.
What the database held
The seven campaigns were separate brand activations in one Firebase project. Two of them held identifying data beyond a photo. The counts are what unauthenticated enumeration reported on 2 June.
- fullName
- athleticScore
- dataSharingConsent
- marketingOptIn
- timestamp
Risk: a name and email from a bank's campaign are enough for a phishing mail in the bank's name. The athletic data makes that mail more convincing.
- fullName
- photoUrl
- consentFlags
Risk: face, name and email in one record. With those three, an attacker can find the same person on other sites.
- photoUrl
- timestamp
Risk: the campaign I tested. Photos with timestamps, which can place a person at a time and location when matched against other data.
- photoUrl
- timestamp
Risk: more stored photos, from a separate activation in the same open project.
Who was responsible
Under GDPR, the brands that collected the data are the controllers, and they have the duty to notify. The AR studio ran the backend and is the processor. One vendor made the mistake, but all four had obligations, so I sent the report to all four at once.
Built and operated the Firebase backend. The misconfiguration lived here.
Ran the AR promo that I tested. Its participants' photos were in the open data.
The fitness activation held more than 1,000 name-and-email records. Bank customers are a valuable phishing target.
The venue mirror stored face, name and email together, the most complete records in the project.
Read-only, and still High
The score assumes that nobody could change or delete data. I did not test writes, because a write would have changed their data. Read access alone is serious for this data. Names with email addresses make targeted phishing easy. Faces with names let an attacker link one person across sites, and the bank campaign adds a reason to impersonate the bank.
- AV:N
- Network reachable. A plain HTTPS request from anywhere.
- AC:L
- Low complexity. Two documented endpoint calls, no special conditions.
- PR:N / UI:N
- No privileges, no user to trick. Fully unauthenticated and automatable.
- C:H
- High confidentiality impact. More than 1,500 records with names, email addresses and photos.
- I:N / A:N
- No integrity or availability impact assumed. I tested reads only, and the service stayed up.
Three lessons for Firebase projects
The API key in your bundle is meant to be public. The security rules are the only access control. A bare allow read: if true puts the whole database on the open web.
A campaign backend is built fast and taken down late. Close any rule you opened for the demo before launch, and check it again after launch. A temporary rule stays until someone removes it.
Closing the database does not expire links that were already handed out. Sign URLs for sensitive files for minutes, not years. After an exposure, rotate the signing keys so that old URLs stop working.
Why this write-up exists
The supermarket and the bank paid through the disclosure platform, and the AR studio sent a goodwill payment, €900 in total. I reported it because the people who used these booths did not agree to have their photos in an open database. I am writing it up now that it is fixed, with the technical detail and without any personal data, because the next campaign backend can make the same mistake just as easily.
If you build these activations, check your security rules before the booth goes live.
All access was unauthenticated and read-only, and limited to proof of access. I did not download, keep or share personal data, and none appears in this write-up. Reported to the AR studio and the three brands on 2 June 2026. The AR studio confirmed full remediation on 17 June 2026. Company names are left out on purpose. Published after remediation.