<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jacob Alcock</title>
    <description>The latest articles on DEV Community by Jacob Alcock (@jacobdavidalcock).</description>
    <link>https://dev.to/jacobdavidalcock</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3599962%2F63d0778c-639f-413b-b97e-c46e7af0d8b8.jpeg</url>
      <title>DEV Community: Jacob Alcock</title>
      <link>https://dev.to/jacobdavidalcock</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jacobdavidalcock"/>
    <language>en</language>
    <item>
      <title>Model Collapse: The AI Feedback Loop Problem Nobody Wants to Talk About</title>
      <dc:creator>Jacob Alcock</dc:creator>
      <pubDate>Mon, 10 Nov 2025 13:31:41 +0000</pubDate>
      <link>https://dev.to/jacobdavidalcock/model-collapse-the-ai-feedback-loop-problem-nobody-wants-to-talk-about-1hpm</link>
      <guid>https://dev.to/jacobdavidalcock/model-collapse-the-ai-feedback-loop-problem-nobody-wants-to-talk-about-1hpm</guid>
      <description>&lt;p&gt;AI models are eating their own tail, and it's going to be a problem.&lt;/p&gt;

&lt;p&gt;The entire premise of modern LLMs is that they're trained on human-generated content. Books, articles, research papers, Stack Overflow answers, GitHub repositories - billions of tokens of actual human knowledge. But that assumption is breaking down faster than anyone wants to admit.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Core Issue
&lt;/h1&gt;

&lt;p&gt;As we approach the end of 2025, the web is saturated with AI-generated content:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Stack Overflow answers copy-pasted from ChatGPT&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GitHub repos with AI-generated documentation and comments&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Blog posts churned out by content farms using GPT&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Social media posts from bots&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Technical articles written entirely by LLMs&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yet AI companies still scrape the web for training data. They can't reliably distinguish human content from synthetic content. Which means &lt;strong&gt;the next generation of models will inevitably train on the outputs of previous models&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is model collapse. And it's not theoretical - it's measurable, reproducible, and already happening.&lt;/p&gt;

&lt;h1&gt;
  
  
  How Model Collapse Works
&lt;/h1&gt;

&lt;p&gt;The feedback loop is straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Gen 1&lt;/strong&gt;: Train on 95% human data, 5% AI slop → minor quality issues&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Gen 2&lt;/strong&gt;: Train on 80% human data, 20% AI content → noticeable degradation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Gen 3&lt;/strong&gt;: Train on 60% human data, 40% AI outputs → significant problems&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Gen 4&lt;/strong&gt;: Train on majority AI-generated content → model collapse&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each generation compounds the problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Loss of diversity&lt;/strong&gt; - outputs converge toward homogeneous, repetitive patterns&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Amplified biases&lt;/strong&gt; - quirks from previous models get magnified&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Increased hallucinations&lt;/strong&gt; - errors stack across generations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tail knowledge disappears&lt;/strong&gt; - rare but critical information gets filtered out first&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's the same principle as photocopying a photocopy. Each iteration degrades the original.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why You Should Care
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Code quality degradation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If Copilot trains on AI-generated code that was itself generated by an earlier model, code suggestions degrade. You're not getting patterns from experienced developers anymore - you're getting averaged-out slop that "looks" like code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security implications&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI-assisted security tools trained on AI-generated vulnerability analyses will miss things. If the training data is full of hallucinated CVE details or incorrect exploit explanations, the model learns wrong information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Knowledge erosion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Niche technical knowledge - the kind buried in obscure forum posts, old mailing lists, and forgotten documentation - disappears first. AI models optimise for common patterns. Rare but critical knowledge gets filtered out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trust degradation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can't tell anymore if that blog post explaining a security vulnerability was written by someone who actually found and tested it, or by an LLM that pieced together fragments from six different sources and hallucinated the rest.&lt;/p&gt;

&lt;h1&gt;
  
  
  Proposed Solutions (And Why They're All Flawed)
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Watermarking&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Embed cryptographic signatures in AI outputs to filter them during training. Google and OpenAI are researching this. Problem: watermarks can be stripped. It's an arms race.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Provenance tracking&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Track the origin of all training data. Only use verified human content. Problem: doesn't scale. The entire value proposition of LLMs is training on massive web-scale datasets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Curated datasets&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Stop scraping the web entirely. Build human-verified, high-quality datasets. Problem: expensive, slow, and fundamentally limits what the model can learn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adversarial filtering&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Train models to detect and exclude AI-generated text. Problem: classic adversarial arms race. Detection improves, generation improves to evade detection, repeat forever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Controlled synthetic mixing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Carefully balance the ratio of real to synthetic data. Problem: requires knowing the exact contamination threshold, which varies by domain and model architecture.&lt;/p&gt;

&lt;p&gt;None of these solve the core issue. And we might already be past the point of no return. The web is saturated with AI slop. Even if filtering started today, there are years of contamination already baked into datasets.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Actual Problem
&lt;/h1&gt;

&lt;p&gt;We're running a one-way experiment on the future of LLMs, and nobody knows the safe parameters.&lt;/p&gt;

&lt;p&gt;No one knows what percentage of AI contamination causes collapse. No one knows if current models are already degraded. No one knows how to reverse contamination once it's in the dataset.&lt;/p&gt;

&lt;p&gt;LLMs were built on the assumption of abundant, renewable human knowledge. But that assumption was wrong. We're strip-mining the web for training data, and the mine doesn't refill. Every piece of human writing that gets replaced with AI slop permanently degrades the training pool.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Economic Incentive Problem
&lt;/h1&gt;

&lt;p&gt;The economics make this worse. AI companies have no incentive to solve this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Scraping is free (legally questionable, but free)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Filtering costs money&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Competition doesn't care about data quality 5 years from now&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Investors reward shipping features, not long-term dataset integrity&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Publishers can't win either. Paywalling content to prevent scraping also blocks legitimate human readers. Not paywalling means getting drained by RAG systems that plagiarise without attribution.&lt;/p&gt;

&lt;p&gt;Content creators lose traffic and revenue to AI summaries. So they either stop producing content (reducing the pool of human knowledge) or start using AI to produce more content faster (contaminating the pool).&lt;/p&gt;

&lt;p&gt;It's a race to the bottom, and every participant is incentivised to make it worse.&lt;/p&gt;

&lt;h1&gt;
  
  
  What Actually Needs to Happen
&lt;/h1&gt;

&lt;p&gt;The realistic options are limited:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Legislation requiring training data transparency&lt;/strong&gt; - companies must disclose what they trained on and prove licensing rights&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mandatory AI content labeling&lt;/strong&gt; - cryptographic signatures that can't be easily stripped&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Royalty systems for scraped content&lt;/strong&gt; - similar to how music licensing works&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Incentivise human-generated content&lt;/strong&gt; - platforms that verify and reward genuine human writing&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of this will happen voluntarily. The industry is too profitable and moving too fast. Regulation would need to come first, and regulators barely understand the technology.&lt;/p&gt;

&lt;p&gt;More likely: we hit model collapse in 3-5 years, everyone scrambles to fix it retroactively, and we end up with some half-botched solution that only partially works.&lt;/p&gt;

&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Model collapse is not a hypothetical future problem. It's happening now, measurably, in controlled experiments. The only question is whether we're already seeing it in production models.&lt;/p&gt;

&lt;p&gt;The feedback loop is real. The economic incentives ensure it will continue. And the proposed solutions all have fundamental flaws that make them unlikely to work at scale.&lt;/p&gt;

&lt;p&gt;I'm not saying LLMs are doomed. I'm saying the current trajectory is unsustainable, and nobody with the power to fix it has an incentive to do so. The companies building these models are optimising for next quarter's revenue, not training data quality in 2030.&lt;/p&gt;

&lt;p&gt;This will either get fixed through heavy-handed regulation, or we'll collectively find out what happens when AI models train on increasingly degraded synthetic data. My money is on the latter.&lt;/p&gt;

&lt;p&gt;The snake is already eating its tail. We're just waiting to see how far down it gets before someone notices.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Research&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://openreview.net/forum?id=5B2K4LRgmz" rel="noopener noreferrer"&gt;Is Model Collapse Inevitable? (Matthias Gerstgrasser et al., 2024)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Curse of Recursion (&lt;a href="https://arxiv.org/abs/2305.17493" rel="noopener noreferrer"&gt;Ilia Shumailov&lt;/a&gt; &lt;a href="https://arxiv.org/abs/2305.17493" rel="noopener noreferrer"&gt;et al., 2024)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AI models collapse when trained on recursively generated data (&lt;a href="https://www.nature.com/articles/s41586-024-07566-y" rel="noopener noreferrer"&gt;Ilia Shumailov&lt;/a&gt; &lt;a href="https://www.nature.com/articles/s41586-024-07566-y" rel="noopener noreferrer"&gt;et al., 2024)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.cs.ox.ac.uk/news/2356-full.html" rel="noopener noreferrer"&gt;New research warns of potential ‘collapse’ of machine learning models&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>llm</category>
    </item>
    <item>
      <title>How to Write Secure Firebase Rules</title>
      <dc:creator>Jacob Alcock</dc:creator>
      <pubDate>Mon, 10 Nov 2025 13:30:40 +0000</pubDate>
      <link>https://dev.to/jacobdavidalcock/how-to-write-secure-firebase-rules-1h1b</link>
      <guid>https://dev.to/jacobdavidalcock/how-to-write-secure-firebase-rules-1h1b</guid>
      <description>&lt;p&gt;Firebase Security Rules are the only thing protecting your data from unauthorised access. This guide covers how to write rules that actually secure your app.&lt;/p&gt;

&lt;h1&gt;
  
  
  Understanding the Basics
&lt;/h1&gt;

&lt;p&gt;Firebase Security Rules work by matching paths and applying conditions. If the condition evaluates to &lt;code&gt;true&lt;/code&gt;, the request is allowed. If &lt;code&gt;false&lt;/code&gt;, it's denied.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloud Firestore Rules Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Your rules go here
    match /collection/{document} {
      allow read, write: if &amp;lt;condition&amp;gt;;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Realtime Database Rules Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "rules": {
    "path": {
      ".read": "&amp;lt;condition&amp;gt;",
      ".write": "&amp;lt;condition&amp;gt;"
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Concepts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Match blocks&lt;/strong&gt;: Define which paths the rule applies to&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Allow statements&lt;/strong&gt;: Specify what operations are permitted&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Conditions&lt;/strong&gt;: Boolean expressions that grant or deny access&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Variables&lt;/strong&gt;: &lt;code&gt;request&lt;/code&gt; (incoming request data) and &lt;code&gt;resource&lt;/code&gt; (existing data)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Rule Methods
&lt;/h1&gt;

&lt;p&gt;Firestore rules support granular methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;read&lt;/code&gt;: Covers both &lt;code&gt;get&lt;/code&gt; (single document) and &lt;code&gt;list&lt;/code&gt; (queries)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;write&lt;/code&gt;: Covers &lt;code&gt;create&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt;, and &lt;code&gt;delete&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;get&lt;/code&gt;: Read a single document&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;list&lt;/code&gt;: Read queries and collections&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;create&lt;/code&gt;: Write new documents&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;update&lt;/code&gt;: Modify existing documents&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;delete&lt;/code&gt;: Remove documents&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Granular control
match /posts/{postId} {
  allow get: if true;  // Anyone can read a single post
  allow list: if request.auth != null;  // Only authenticated users can query
  allow create: if request.auth != null;  // Only authenticated users can create
  allow update: if request.auth.uid == resource.data.authorId;  // Only author can update
  allow delete: if request.auth.uid == resource.data.authorId;  // Only author can delete
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Common Secure Patterns
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Pattern 1: User Can Only Access Their Own Data
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use case&lt;/strong&gt;: User profiles, private documents, personal settings&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Firestore&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;match /users/{userId} {
  allow read, write: if request.auth != null &amp;amp;&amp;amp; request.auth.uid == userId;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Realtime Database&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "rules": {
    "users": {
      "$userId": {
        ".read": "$userId === auth.uid",
        ".write": "$userId === auth.uid"
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pattern 2: Public Read, Authenticated Write
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use case&lt;/strong&gt;: Blog posts, public content, product listings&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Firestore&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;match /posts/{postId} {
  allow read: if true;
  allow create: if request.auth != null;
  allow update, delete: if request.auth != null
                         &amp;amp;&amp;amp; request.auth.uid == resource.data.authorId;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Realtime Database&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "rules": {
    "posts": {
      "$postId": {
        ".read": true,
        ".write": "auth != null &amp;amp;&amp;amp; (!data.exists() || data.child('authorId').val() === auth.uid)"
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pattern 3: Role-Based Access Using Custom Claims
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use case&lt;/strong&gt;: Admin panels, multi-role applications&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup custom claims&lt;/strong&gt; (server-side):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const admin = require('firebase-admin');

// Set custom claims
await admin.auth().setCustomUserClaims(uid, { admin: true });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Firestore rules&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;match /adminData/{document} {
  allow read, write: if request.auth.token.admin == true;
}

match /posts/{postId} {
  allow read: if true;
  allow write: if request.auth.token.editor == true
               || request.auth.token.admin == true;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Realtime Database&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "rules": {
    "adminData": {
      ".read": "auth.token.admin === true",
      ".write": "auth.token.admin === true"
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pattern 4: Data Validation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use case&lt;/strong&gt;: Ensuring data format and required fields&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Firestore&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;match /posts/{postId} {
  allow create: if request.auth != null
                &amp;amp;&amp;amp; request.resource.data.keys().hasAll(['title', 'content', 'authorId'])
                &amp;amp;&amp;amp; request.resource.data.title is string
                &amp;amp;&amp;amp; request.resource.data.title.size() &amp;gt; 0
                &amp;amp;&amp;amp; request.resource.data.title.size() &amp;lt; 200
                &amp;amp;&amp;amp; request.resource.data.authorId == request.auth.uid;

  allow update: if request.auth != null
                &amp;amp;&amp;amp; request.auth.uid == resource.data.authorId
                &amp;amp;&amp;amp; request.resource.data.authorId == resource.data.authorId; // Prevent changing author
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Realtime Database&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "rules": {
    "posts": {
      "$postId": {
        ".write": "auth != null &amp;amp;&amp;amp; newData.hasChildren(['title', 'content', 'authorId'])",
        "title": {
          ".validate": "newData.isString() &amp;amp;&amp;amp; newData.val().length &amp;gt; 0 &amp;amp;&amp;amp; newData.val().length &amp;lt; 200"
        },
        "authorId": {
          ".validate": "newData.val() === auth.uid &amp;amp;&amp;amp; (!data.exists() || data.val() === newData.val())"
        }
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pattern 5: Attribute-Based Access (Data-Driven Roles)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use case&lt;/strong&gt;: Shared documents, team access, permission-based systems&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Firestore&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;match /projects/{projectId} {
  allow read: if request.auth != null
              &amp;amp;&amp;amp; request.auth.uid in resource.data.members;

  allow write: if request.auth != null
               &amp;amp;&amp;amp; request.auth.uid in resource.data.admins;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Realtime Database&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "rules": {
    "projects": {
      "$projectId": {
        ".read": "auth != null &amp;amp;&amp;amp; data.child('members').child(auth.uid).exists()",
        ".write": "auth != null &amp;amp;&amp;amp; data.child('admins').child(auth.uid).exists()"
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Using Functions for Reusable Logic
&lt;/h1&gt;

&lt;p&gt;Functions make rules more maintainable and readable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // Check if user is authenticated
    function isSignedIn() {
      return request.auth != null;
    }

    // Check if user owns the resource
    function isOwner(userId) {
      return request.auth.uid == userId;
    }

    // Check if user has a specific role
    function hasRole(role) {
      return isSignedIn() &amp;amp;&amp;amp; request.auth.token[role] == true;
    }

    // Validate required fields
    function hasRequiredFields(fields) {
      return request.resource.data.keys().hasAll(fields);
    }

    // Use the functions
    match /users/{userId} {
      allow read: if isSignedIn();
      allow write: if isOwner(userId);
    }

    match /posts/{postId} {
      allow create: if isSignedIn()
                    &amp;amp;&amp;amp; hasRequiredFields(['title', 'content', 'authorId'])
                    &amp;amp;&amp;amp; isOwner(request.resource.data.authorId);

      allow update: if isOwner(resource.data.authorId);
      allow delete: if isOwner(resource.data.authorId) || hasRole('admin');
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Handling Subcollections
&lt;/h1&gt;

&lt;p&gt;In Firestore, rules don't cascade to subcollections. You must explicitly define rules for each level.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;match /users/{userId} {
  allow read: if request.auth.uid == userId;

  // Subcollection requires its own rules
  match /privateData/{document} {
    allow read, write: if request.auth.uid == userId;
  }

  // Another subcollection
  match /posts/{postId} {
    allow read: if true;  // Public read
    allow write: if request.auth.uid == userId;  // Only owner can write
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: A match like &lt;code&gt;/users/{userId}/{document=**}&lt;/code&gt; will match ALL nested subcollections recursively. Use this carefully.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// This matches /users/{userId}/anything/at/any/depth
match /users/{userId}/{document=**} {
  allow read: if request.auth.uid == userId;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Realtime Database: Cascading Rules
&lt;/h1&gt;

&lt;p&gt;In Realtime Database, rules CASCADE. Parent rules override child rules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "rules": {
    "users": {
      // This grants read access to all user data
      ".read": "auth != null",
      "$userId": {
        // This CANNOT restrict the read access granted above
        ".read": "$userId === auth.uid",  // This is IGNORED
        ".write": "$userId === auth.uid"
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Correct approach&lt;/strong&gt;: Don't grant broad access at parent levels.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "rules": {
    "users": {
      "$userId": {
        ".read": "$userId === auth.uid",
        ".write": "$userId === auth.uid"
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Testing Your Rules
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Use FireScan
&lt;/h2&gt;

&lt;p&gt;Try out my purpose built tool for auditing firebase infrastructure. It’s completely free, open-source and available for anyone to use. Check it out &lt;a href="https://firescan.jacobalcock.co.uk/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the Firebase Emulator
&lt;/h2&gt;

&lt;p&gt;Install and run locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -g firebase-tools
firebase init emulators
firebase emulators:start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Use the Rules Simulator in Firebase Console
&lt;/h2&gt;

&lt;p&gt;Navigate to Firestore/Realtime Database → Rules → Playground&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Select operation type (get, list, create, etc.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose authenticated or unauthenticated&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Specify the path&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run simulation&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is useful for quick checks but not a substitute for proper testing.&lt;/p&gt;

&lt;h1&gt;
  
  
  Common Mistakes to Avoid
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. Using &lt;code&gt;if true&lt;/code&gt; in Production
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// NEVER DO THIS
match /{document=**} {
  allow read, write: if true;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Relying Only on &lt;code&gt;request.auth != null&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// This allows ANY authenticated user to access ANY data
match /users/{userId} {
  allow read, write: if request.auth != null;  // Too permissive
}

// Better: verify the user matches
match /users/{userId} {
  allow read, write: if request.auth != null &amp;amp;&amp;amp; request.auth.uid == userId;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Forgetting Realtime Database Cascade Rules
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "rules": {
    "data": {
      ".read": true,  // Grants read to everything below
      "private": {
        ".read": false  // This is IGNORED, read was already granted above
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Not Validating Data on Create/Update
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Bad: No validation
match /posts/{postId} {
  allow create: if request.auth != null;
}

// Good: Validate required fields and author
match /posts/{postId} {
  allow create: if request.auth != null
                &amp;amp;&amp;amp; request.resource.data.keys().hasAll(['title', 'content', 'authorId'])
                &amp;amp;&amp;amp; request.resource.data.authorId == request.auth.uid;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Allowing Field Modification That Shouldn't Change
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Bad: User can change the author
match /posts/{postId} {
  allow update: if request.auth.uid == resource.data.authorId;
}

// Good: Prevent changing the author field
match /posts/{postId} {
  allow update: if request.auth.uid == resource.data.authorId
                &amp;amp;&amp;amp; request.resource.data.authorId == resource.data.authorId;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Overusing &lt;code&gt;get()&lt;/code&gt; and &lt;code&gt;exists()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Each &lt;code&gt;get()&lt;/code&gt; or &lt;code&gt;exists()&lt;/code&gt; call in your rules counts as a read operation and costs money. You're also limited to 10 calls per request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Bad: Multiple get() calls
match /posts/{postId} {
  allow read: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'reader'
              || get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin';
}

// Better: Use custom claims or structure data differently
match /posts/{postId} {
  allow read: if request.auth.token.reader == true
              || request.auth.token.admin == true;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Version Control Your Rules
&lt;/h1&gt;

&lt;p&gt;Keep your rules in source control alongside your code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add to&lt;/strong&gt; &lt;code&gt;.gitignore&lt;/code&gt; if needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Don't ignore rules files
!firestore.rules
!database.rules.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt; &lt;code&gt;firestore.rules&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // All your rules here
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Deploy with Firebase CLI&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;firebase deploy --only firestore:rules
firebase deploy --only database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Deployment Checklist
&lt;/h1&gt;

&lt;p&gt;Before deploying rules to production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Remove all &lt;code&gt;if true&lt;/code&gt; or &lt;code&gt;if false&lt;/code&gt; test rules&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Verify authentication checks on all sensitive paths&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Test rules using the emulator with unit tests&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check for cascading rule issues (Realtime Database)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Validate required fields on create/update operations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensure users can't modify fields they shouldn't (like &lt;code&gt;authorId&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Review &lt;code&gt;get()&lt;/code&gt; and &lt;code&gt;exists()&lt;/code&gt; usage (limit of 10 per request)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Test with authenticated and unauthenticated contexts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Version control your rules&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use &lt;code&gt;firebase deploy --only firestore:rules&lt;/code&gt; (don't deploy everything)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Complete Example: Blog Application
&lt;/h1&gt;

&lt;p&gt;Here's a complete, production-ready ruleset for a blog app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // Helper functions
    function isSignedIn() {
      return request.auth != null;
    }

    function isOwner(uid) {
      return isSignedIn() &amp;amp;&amp;amp; request.auth.uid == uid;
    }

    function isAdmin() {
      return isSignedIn() &amp;amp;&amp;amp; request.auth.token.admin == true;
    }

    // User profiles
    match /users/{userId} {
      allow read: if isSignedIn();
      allow create: if isOwner(userId)
                    &amp;amp;&amp;amp; request.resource.data.keys().hasAll(['displayName', 'email'])
                    &amp;amp;&amp;amp; request.resource.data.email == request.auth.token.email;
      allow update: if isOwner(userId)
                    &amp;amp;&amp;amp; request.resource.data.email == resource.data.email; // Prevent email change
      allow delete: if isOwner(userId) || isAdmin();
    }

    // Blog posts
    match /posts/{postId} {
      allow read: if resource.data.published == true || isOwner(resource.data.authorId) || isAdmin();
      allow create: if isSignedIn()
                    &amp;amp;&amp;amp; request.resource.data.keys().hasAll(['title', 'content', 'authorId', 'published', 'createdAt'])
                    &amp;amp;&amp;amp; isOwner(request.resource.data.authorId)
                    &amp;amp;&amp;amp; request.resource.data.title is string
                    &amp;amp;&amp;amp; request.resource.data.title.size() &amp;gt; 0
                    &amp;amp;&amp;amp; request.resource.data.title.size() &amp;lt;= 200
                    &amp;amp;&amp;amp; request.resource.data.createdAt == request.time;
      allow update: if isOwner(resource.data.authorId)
                    &amp;amp;&amp;amp; request.resource.data.authorId == resource.data.authorId  // Prevent author change
                    &amp;amp;&amp;amp; request.resource.data.createdAt == resource.data.createdAt;  // Prevent timestamp change
      allow delete: if isOwner(resource.data.authorId) || isAdmin();

      // Comments subcollection
      match /comments/{commentId} {
        allow read: if true;
        allow create: if isSignedIn()
                      &amp;amp;&amp;amp; request.resource.data.keys().hasAll(['text', 'authorId', 'createdAt'])
                      &amp;amp;&amp;amp; isOwner(request.resource.data.authorId)
                      &amp;amp;&amp;amp; request.resource.data.text.size() &amp;gt; 0
                      &amp;amp;&amp;amp; request.resource.data.text.size() &amp;lt;= 1000;
        allow update: if isOwner(resource.data.authorId)
                      &amp;amp;&amp;amp; request.resource.data.authorId == resource.data.authorId;
        allow delete: if isOwner(resource.data.authorId) || isAdmin();
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Default to denying access&lt;/strong&gt;. Only grant permissions where specifically needed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Always verify authentication&lt;/strong&gt; with &lt;code&gt;request.auth != null&lt;/code&gt; and check user ownership.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Validate data&lt;/strong&gt; on create and update operations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prevent field tampering&lt;/strong&gt; by ensuring critical fields don't change on update.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use custom claims&lt;/strong&gt; for roles instead of repeated &lt;code&gt;get()&lt;/code&gt; calls.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test your rules&lt;/strong&gt; with the emulator and unit tests before deploying.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Version control&lt;/strong&gt; your rules and review changes like code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Understand cascading&lt;/strong&gt; (Realtime Database) vs explicit subcollection rules (Firestore).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Firebase Security Rules are powerful but require careful implementation. Take the time to write them correctly, test them thoroughly, and audit them regularly.&lt;/p&gt;

&lt;p&gt;Your rules are the only thing standing between your data and unauthorised access. Make them count.&lt;/p&gt;

</description>
      <category>firebase</category>
      <category>webdev</category>
      <category>programming</category>
      <category>security</category>
    </item>
    <item>
      <title>Firebase Security Is Broken. Here's the Tool I Built to Fix It.</title>
      <dc:creator>Jacob Alcock</dc:creator>
      <pubDate>Fri, 07 Nov 2025 09:00:00 +0000</pubDate>
      <link>https://dev.to/jacobdavidalcock/firebase-security-is-broken-heres-the-tool-i-built-to-fix-it-4kdl</link>
      <guid>https://dev.to/jacobdavidalcock/firebase-security-is-broken-heres-the-tool-i-built-to-fix-it-4kdl</guid>
      <description>&lt;p&gt;A couple of months ago I was doing a few penetration tests recently when I encountered Firebase configurations. Each time, I found myself stringing together a bunch of cURL commands and one-off Python scripts to check for common misconfigurations. After the third engagement, I realised this was pretty inefficient.&lt;/p&gt;

&lt;p&gt;I was looking for a tool where I could just set the configuration and run enumeration checks. Something like &lt;code&gt;msfconsole&lt;/code&gt; but for Firebase. I couldn't find anything that fit the bill, so &lt;strong&gt;I built it myself.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Firebase is incredibly popular - it powers millions of apps. But its security model is... tricky. The core issue is that Firebase uses declarative security rules. A single &lt;code&gt;||&lt;/code&gt; operator in the wrong place can expose your entire database.&lt;/p&gt;

&lt;p&gt;During pentests, I kept seeing the same patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;RTDB nodes readable without authentication&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Firestore collections with open read rules&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cloud Storage buckets listing all files&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cloud Functions without proper auth checks&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://www.youtube.com/watch?v=npfUPhu2aZg&amp;amp;t=184s" rel="noopener noreferrer"&gt;Tea app breach&lt;/a&gt; is a perfect example - misconfigured Firestore rules exposed sensitive user data. This wasn't a sophisticated attack, it was just someone checking if default or weak rules were still in place.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Wanted
&lt;/h2&gt;

&lt;p&gt;Coming from a pentesting background, I needed something that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Works with minimal information&lt;/strong&gt; (i.e. Just the projectID and web API key)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tests comprehensively&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Is safe by default&lt;/strong&gt; (Won't accidentally damage production data)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Handles authentication properly&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scales to large wordlists&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of the existing tools checked all these boxes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing FireScan
&lt;/h2&gt;

&lt;p&gt;FireScan is a tool designed for penetration testers and developers to audit the security posture of Firebase projects. It provides an interactive console to enumerate databases, test storage rules, check function security, and much more, all from a single, easy-to-use interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;firescan
███████╗██╗██████╗ ███████╗███████╗ ██████╗ █████╗ ███╗   ██╗
██╔════╝██║██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗████╗  ██║
█████╗  ██║██████╔╝█████╗  ███████╗██║     ███████║██╔██╗ ██║
██╔══╝  ██║██╔══██╗██╔══╝  ╚════██║██║     ██╔══██║██║╚██╗██║
██║     ██║██║  ██║███████╗███████║╚██████╗██║  ██║██║ ╚████║
╚═╝     ╚═╝╚═╝  ╚═╝╚══════╝╚══════╝ ╚═════╝╚═╝  ╚═╝╚═╝  ╚═══╝

FireScan v1.0 - The Firebase Security Auditor

firescan &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;set &lt;/span&gt;projectID my-app-12345
firescan &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;set &lt;/span&gt;apiKey AIza...
firescan &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; auth &lt;span class="nt"&gt;--create-account&lt;/span&gt;
✓ Successfully authenticated
firescan &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; scan &lt;span class="nt"&gt;--all&lt;/span&gt;
Key Features
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;Here's a real scenario from a recent test without the real data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;firescan &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;set &lt;/span&gt;projectID example-app-abc123 
firescan &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;set &lt;/span&gt;apiKey AIzaSy... 
firescan &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; auth &lt;span class="nt"&gt;--create-account&lt;/span&gt;
firescan &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; scan &lt;span class="nt"&gt;--firestore&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; all
&lt;span class="o"&gt;[&lt;/span&gt;✓] Scanning... &lt;span class="o"&gt;[&lt;/span&gt;Checked: 200/200 | Found: 4]

&lt;span class="o"&gt;[&lt;/span&gt;Firestore] Vulnerability Found! ├── Timestamp: 2025-01-15T10:23:45Z ├── Severity: High ├── Type: Firestore └── Path: &lt;span class="nb"&gt;users&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;Firestore] Vulnerability Found! ├── Timestamp: 2025-01-15T10:23:47Z ├── Severity: High ├── Type: Firestore └── Path: messages

firescan &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; extract &lt;span class="nt"&gt;--firestore&lt;/span&gt; &lt;span class="nt"&gt;--path&lt;/span&gt; &lt;span class="nb"&gt;users&lt;/span&gt; 
&lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"documents"&lt;/span&gt;: 
    &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"DOCUMENT_ID"&lt;/span&gt;: &lt;span class="s2"&gt;"user_12345"&lt;/span&gt;, &lt;span class="s2"&gt;"email"&lt;/span&gt;: &lt;span class="s2"&gt;"john.doe@example.com"&lt;/span&gt;, &lt;span class="s2"&gt;"name"&lt;/span&gt;: &lt;span class="s2"&gt;"John Doe"&lt;/span&gt;, ... &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; 
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In under 2 minutes, I found two readable collections and extracted the data. Without FireScan, this would have taken me 20 minutes of manual curl commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Try It Out&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/JacobDavidAlcock/firescan" rel="noopener noreferrer"&gt;&lt;strong&gt;https://github.com/JacobDavidAlcock/firescan&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>firebase</category>
      <category>programming</category>
      <category>security</category>
    </item>
  </channel>
</rss>
