Rate Limiting Bypass in Chi with Firestore
Rate Limiting Bypass in Chi with Firestore — how this specific combination creates or exposes the vulnerability
Rate limiting is a control that restricts the number of requests a client can make to an endpoint in a given time window. When an API is implemented in Chi (a minimalistic routing library for Clojure) and backed by Google Firestore, misconfigured or absent rate limits can allow an attacker to exceed intended request volumes, leading to abuse such as brute force, scraping, or denial-of-service. middleBrick checks for missing or weak rate limiting as part of its 12 parallel security checks and reports the severity when an endpoint is unauthenticated or otherwise excessively permissive.
In a Chi service, routes are typically defined as a sequence of middleware and handlers. If a route that reads or writes Firestore lacks a rate-limiting layer, an attacker can send many requests in rapid succession. Firestore itself enforces quotas at the project level, but these are coarse and not a substitute for per-client or per-endpoint controls. A missing per-route limiter in Chi means requests are passed to Firestore without throttling, allowing enumeration of document IDs via timing differences or error responses, or triggering expensive operations such as writes or batch transactions.
Consider a Chi endpoint that retrieves a user document by ID directly from Firestore without any guard:
(ns example.api
(:require [cheshire.core :as json]
[clj-http.client :as http]
[com.google.cloud.firestore :as firestore]
[ring.util.response :as resp]
[hiccup.core :refer [html]]
[compojure.core :refer [GET POST]]))
(defonce db (firestore/FirestoreOptions/getDefaultInstance))
(defn get-user [req]
(let [user-id (get-in req [:params :id])
doc (firestore/get-document db (str "users/" user-id))]
(resp/response (json/generate-string doc))))
(def app
(routes
(GET "/user/:id" [] get-user)))
If this route is publicly reachable and lacks a rate limiter, an attacker can iterate over user IDs rapidly. Firestore will return documents when they exist and 404-like responses when they do not, allowing enumeration. Even when Firestore returns generic errors or quota-related responses, timing differences may reveal whether a given ID exists. middleBrick detects such endpoints during its runtime testing and highlights the absence of rate limiting as a high-severity finding.
Another scenario involves write paths that create or update Firestore documents. Without rate limiting, an attacker can spam writes, consuming quotas and potentially triggering contention or cost amplification. For example, a Chi POST handler that creates a document for every request can be exploited to exhaust write operations or to inject large volumes of data:
(defn create-note [req]
(let [user-id (get-in req [:params :user-id])
note-content (get-in req [:params :content])
note-id (str (java.util.UUID/randomUUID))
doc-data {:content note-content :created-at (java.util.Date.)}]
(firestore/set-document db (str "users/" user-id "/notes/" note-id) doc-data)
(resp/response "ok")))
middleBrick flags this pattern when combined with missing rate limiting, because the endpoint is unauthenticated or improperly scoped, and the Firestore operations are unrestricted. The scanner does not invoke destructive actions but it tests whether controls such as request throttling are observable and effective.
The interplay between Chi routing and Firestore amplifies risk when developers assume Firestore quotas alone are sufficient. Project-level quotas protect against large-scale abuse but do not prevent targeted enumeration or low-and-slow attacks from a single malicious client. middleBrick validates whether per-endpoint rate limiting exists and whether responses avoid leaking information that could aid an attacker in bypassing other controls.
Firestore-Specific Remediation in Chi — concrete code fixes
Remediation focuses on adding explicit rate-limiting middleware in Chi and hardening Firestore interactions to reduce information leakage. Implement a per-client or per-IP token bucket or fixed-window limiter before Firestore calls. Ensure responses are uniform and avoid leaking existence or ownership details through status codes or timing differences.
Below is a Chi example that applies a simple fixed-window rate limit using an in-memory atom. For production, use a shared store such as Redis with an atomic increment-and-expire pattern, but the structure remains similar:
(ns example.api.rate
(:require [cheshire.core :as json]
[clj-http.client :as http]
[com.google.cloud.firestore :as firestore]
[ring.util.response :as resp]
[hiccup.core :refer [html]]
[compojure.core :refer [GET POST]]))
(def rate-limit 100) ; requests per window
(def window-ms 60000) ; 60 seconds
(def request-count (atom {}))
(defn- allowed? [client-id]
(let [now (System/currentTimeMillis)
counts (swap! request-count
(fn [m]
(let [entry (get m client-id)
ts (:timestamp entry 0)]
(if (> (- now ts) window-ms)
{client-id {:count 1 :timestamp now}}
(update m client-id (fnil update-in [:count]) inc)))))
(<= ((get counts client-id) :count) rate-limit))
(defn get-user [req]
(let [client-id (or (get-in req [:headers "x-forwarded-for"] "unknown")
(str (java.util.UUID/randomUUID)))
_ (when-not (allowed? client-id)
(resp/response (str "rate limit exceeded")))
user-id (get-in req [:params :id])
doc (try
(firestore/get-document db (str "users/" user-id))
(catch Exception _ nil))]
(if doc
(resp/response (json/generate-string doc))
(resp/response (str "not found")))))
Key points in this remediation:
- The limiter uses a client identifier (IP or API key) to scope counts. In production, replace the in-memory atom with a distributed store to avoid bypass via multiple instances.
- Responses for rate-limited requests are generic and do not disclose whether the user ID exists, reducing information leakage.
- The Firestore call is wrapped in a try-catch to avoid exposing stack traces or internal errors; always return uniform error messages and status codes.
For Firestore-specific hardening, avoid returning different HTTP status codes based on document existence. Instead, return a consistent 200 with a body that indicates absence when appropriate, and ensure indexes and queries are designed to prevent enumeration via predictable IDs. If authentication is required, enforce it before the rate limiter so that limits apply per authenticated identity rather than per IP, reducing shared-account abuse.
middleBrick’s scans validate that these controls are observable in runtime behavior. Its findings include actionable remediation guidance, such as introducing per-endpoint throttling, standardizing responses, and aligning with frameworks like OWASP API Top 10 to reduce risk.
Related CWEs: resourceConsumption
| CWE ID | Name | Severity |
|---|---|---|
| CWE-400 | Uncontrolled Resource Consumption | HIGH |
| CWE-770 | Allocation of Resources Without Limits | MEDIUM |
| CWE-799 | Improper Control of Interaction Frequency | MEDIUM |
| CWE-835 | Infinite Loop | HIGH |
| CWE-1050 | Excessive Platform Resource Consumption | MEDIUM |