Firebase
Cross-Site Scripting (XSS)

XSS in Firebase-Powered Apps

Firebase does not introduce XSS vulnerabilities on its own, but the way applications render data retrieved from Firestore, Realtime Database, and Cloud Storage creates XSS vectors. Stored XSS is the primary concern: malicious content saved to Firestore is served to every user who views it.

Scan Your Firebase App

How It Happens

Firebase apps retrieve documents from Firestore and render them in the browser. When a document field contains user-generated content (comments, profile bios, post bodies), developers render it directly without sanitization. The data came from a "trusted" database, so developers assume it is safe, but any authenticated user could have stored malicious HTML in that field. Stored XSS through Firestore is especially dangerous because the payload persists. An attacker writes a document with a malicious script tag in a text field. Every user who subsequently reads that document has the script execute in their browser. Unlike reflected XSS which requires the victim to click a crafted link, stored XSS attacks every viewer automatically. Firebase Hosting with dynamic routing (Cloud Functions or server-side rendering) can also introduce reflected XSS if URL parameters are echoed into HTML responses without encoding. Firebase Hosting itself is secure, but Cloud Functions that generate HTML must handle output encoding.

Impact

Stored XSS in a Firebase app affects every user who views the poisoned document. In a social app, a malicious comment could steal session tokens from thousands of users. In a collaborative tool, a poisoned document could hijack every collaborator's Firebase Auth token. Firebase Auth tokens stored in IndexedDB are accessible to JavaScript running on the page. An XSS exploit can extract the token and send it to an attacker-controlled server, providing persistent access to the victim's account. For Firebase apps with sensitive data, XSS combined with the user's Firebase credentials gives the attacker the ability to query Firestore as the victim, accessing all data the victim's Security Rules allow. This is a full account takeover through the Firebase SDK.

How to Detect

Review how your application renders data from Firestore. Search for dangerouslySetInnerHTML (React), v-html (Vue), or innerHTML assignments that use Firestore document fields. Every instance where Firestore data flows into an HTML rendering method is a potential stored XSS vector. Test by writing a Firestore document with HTML content (like <img src=x onerror=alert(1)>) in a text field. If the app renders the alert when displaying that document, it is vulnerable. Vibe App Scanner detects stored XSS patterns by analyzing how Firebase applications render Firestore data, checking for missing sanitization in the rendering pipeline.

How to Fix

Sanitize all Firestore data before rendering it as HTML. Use DOMPurify to strip malicious content while preserving safe formatting. Create a shared utility function that all components use for rendering user-generated content. Prefer plain text rendering over HTML rendering. Use React's JSX escaping ({firebaseData.field}) instead of dangerouslySetInnerHTML wherever possible. If rich text is needed, use a markdown renderer instead of raw HTML. Implement Firestore Security Rules that validate content on write. Use rules to reject documents that contain HTML tags in text fields: !request.resource.data.content.matches('.*<[a-zA-Z].*') prevents storing HTML in content fields. Add a Content-Security-Policy header to your Firebase Hosting configuration in firebase.json. Set script-src to block inline scripts as a defense-in-depth measure against XSS.

Code Examples

Rendering Firestore data in React

Vulnerable
// Rendering Firestore document content as HTML
function Post({ postId }: { postId: string }) {
  const [post, setPost] = useState<any>(null)
  useEffect(() => {
    getDoc(doc(db, 'posts', postId))
      .then(snap => setPost(snap.data()))
  }, [postId])
  return (
    <div dangerouslySetInnerHTML={{ __html: post?.body }} />
  )
}
Secure
import DOMPurify from 'dompurify'

function Post({ postId }: { postId: string }) {
  const [post, setPost] = useState<any>(null)
  useEffect(() => {
    getDoc(doc(db, 'posts', postId))
      .then(snap => setPost(snap.data()))
  }, [postId])
  // Option 1: Sanitize HTML
  return (
    <div dangerouslySetInnerHTML={{
      __html: DOMPurify.sanitize(post?.body ?? '')
    }} />
  )
  // Option 2: Render as plain text (safest)
  // return <div>{post?.body}</div>
}

Frequently Asked Questions

Does Firebase sanitize data stored in Firestore?

No. Firestore stores data exactly as written. It does not strip HTML tags or validate content for XSS. Sanitization must happen either when writing the data or when rendering it in the browser.

Can Firestore Security Rules prevent XSS?

Partially. You can write rules that reject documents containing HTML tags in text fields. However, this is a blunt instrument and may block legitimate content. Client-side sanitization with DOMPurify is more flexible and reliable.

Is stored XSS worse than reflected XSS in Firebase apps?

Yes. Stored XSS persists in Firestore and affects every user who views the poisoned document. Reflected XSS requires the victim to click a crafted link. Stored XSS has a much larger blast radius in Firebase apps because Firestore data is typically shared across many users.

Is Your App Vulnerable?

VAS automatically scans for cross-site scripting (xss) and other security issues in Firebase apps. Get actionable results with step-by-step fixes.

Scans from $5, results in minutes.