Skip to content

Build1 publisher3 min readPublished

A generated read-then-write lets both requests spend the same balance

A dev.to walkthrough of AI-written Firebase code argues the dangerous defects sit in the architecture, and its examples are a balance update two concurrent requests can both pass and a rule that admits any signed-in user.

The Engineer · Build desk

Illustration accompanying A generated read-then-write lets both requests spend the same balance

What happened

  • A dev.to walkthrough shows a Firebase balance update in which two near-simultaneous requests both read the same balance, both decide there is enough money, and both write, so one update overwrites the other.
  • It flags the Security Rule an assistant reaches for, allow read, write: if request.auth != null, which admits every signed-in user to documents belonging to other people.
  • The post lists isAdmin, balance, plan, createdAt and updatedAt as fields an assistant may treat as ordinary client-writable properties.
  • Its Cloud Functions example sends a welcome email, fails immediately after the send, and sends the same email again when the platform retries the function.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • exposure Broken Object Level Authorization is what this produces on a live project: one signed-in account reaches every document the rule covers, and the client-side CRUD code looks identical whether the ownership check is there or not.
  • decision Someone has to choose between a transaction, a batched write, and moving the work off the write path into Cloud Tasks before the generated code merges, and that choice lives outside the diff.
  • constraint Sequential tests cannot open the two-statement window, so unit tests and code review stop being the gate for this class of defect and behaviour under concurrent load becomes the acceptance criterion.
  • cost Treating generated Security Rules as code that needs tests and review adds a review step to output that arrived looking finished, and it lands on whoever owns the data model.

Two handlers call getUser, both read the same balance, both pass the comparison, both subtract, and both write [1]. The post calls that a classic race condition and says one update may overwrite the other [2]. Firebase hides enough infrastructure that a small architectural mistake stays invisible until the application is under load [17].

Each of the four statements in the snippet is valid on its own. The snippet reads on its first statement and writes on its fourth, so a competing request has a two-statement window to land in [19]. A test that issues one request at a time keeps that window shut. Reviewers have nothing in the diff to point at, so the defect survives.

The remedy is a design choice, and the post treats it as one. Transactions are for writes that depend on current data; batched writes are for atomic write-only operations [3][4]. For thousands of users joining a queue at once it suggests separating event capture from processing with Cloud Tasks instead of pushing everything through database transactions [5]. The same judgement applies to ordering: do not assume equal sort values will always produce a stable order, and add a reliable secondary ordering when the application depends on it [6].

The item I would put in a checklist is the one about copying rules between products. Realtime Database permissions cascade through the data tree and Firestore rules do not work the same way, so a pattern moved from one to the other can produce serious security problems [10]. Paths in the two products look alike, and that resemblance is what a pattern matcher copies.

Which fields are permissions is something a generator can only guess at from the name. Of the five the post lists, createdAt and updatedAt are bookkeeping, and isAdmin, balance and plan decide what an account is entitled to [20]. It points at Firebase Authentication custom claims for server-controlled roles, and says Security Rules should be "treated as executable security code, tested and reviewed rather than accepted as generated boilerplate" [12][13]. The ownership test it offers is one line: allow write: if request.auth.uid == userId [8].

On retries the post is direct. "The code is syntactically fine. The problem is that the operation is not idempotent," it says of a welcome-email handler [15]. Payments, rewards, notifications, inventory updates and third-party API calls are the operations it names where a second execution matters [16].

The post does not say how often generated Firebase code carries these patterns; its case rests on the examples it walks through. In my view the ordering warning and the cross-product rule copy are the two items a syntax-clean generator will keep getting wrong, because both depend on facts that live outside the file it is editing.

What to watch

  • Measured incidence data on how often generated Firebase code ships a read-then-write outside a transaction would turn this from a walkthrough into a rate.
  • Whether assistants start checking which database a project uses before emitting rules, given that Realtime Database permissions cascade and Firestore's do not.
  • Firebase tooling that fails a build on a rule matching request.auth != null with no ownership test.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories