Cache Poisoning in Gorilla Mux with Firestore
Cache Poisoning in Gorilla Mux with Firestore — how this specific combination creates or exposes the vulnerability
Cache poisoning in the context of Gorilla Mux routing and Google Cloud Firestore occurs when attacker-influenced input leads to the storage or retrieval of unintended data in cache-like layers or through Firestore queries. Gorilla Mux is a widely used HTTP request router and matcher for Go that builds routes with path patterns, query parameters, and headers. When routes are combined with Firestore as a backend data source, unsafe use of request data in query construction or response handling can cause one client’s data to be served to another, or cause writes to be applied to incorrect documents.
Consider a handler built with Gorilla Mux that uses a URL path variable for a Firestore document ID without strict validation:
vars := mux.Vars(request)
docID := vars["docID"]
ctx := context.Background()
client, err := firestore.NewClient(ctx, "my-project")
if err != nil { /* handle error */ }
defer client.Close()
docRef := client.Collection("items").Doc(docID)
snap, err := docRef.Get(ctx)
if err != nil { /* handle error */ }
// Use snap.Data() directly in response or cache key
If docID is derived from unvalidated user input and used directly as a Firestore document key, an attacker can manipulate the path to access or influence documents that should be private to another user. For example, by providing a crafted path such as /items/..%2F..%2Fusers%2Fadmin, an attacker may attempt path traversal to reference a different document, depending on how the routing and escaping are handled. Similarly, query parameters that are reflected into Firestore queries without canonicalization can lead to inconsistent filtering, effectively causing cache or response poisoning when different users receive data they should not see.
Another scenario involves using Firestore field values as cache keys or response selectors without normalization. For instance, if a handler uses a query parameter to select a subcollection or field and then caches the result based on the raw parameter, two different requests that should map to distinct Firestore paths might resolve to the same cache key. This can cause one client’s data to be served to another, which is a practical cache poisoning impact in multi-tenant or user-segmented applications. Firestore rules do not automatically protect against application-level routing mistakes; incorrect document references caused by malformed or malicious route inputs will still result in unauthorized data exposure if the application logic does not enforce strict ownership checks.
In addition, Firestore’s flexible indexing and array-contains queries can amplify issues when user input is used directly in array filters. A handler that builds queries like Where("tags", "array-contains", userTag) with unsanitized userTag can inadvertently cause logical overlaps in cached query results. Two users with different tags might see overlapping data if the query logic does not enforce strict scoping, effectively poisoning the cache of query responses for those endpoints.
These risks are not inherent to Gorilla Mux or Firestore individually, but emerge from the combination of flexible routing, dynamic query building, and insufficient input canonicalization and ownership validation. Proper mitigation requires strict validation of path variables, canonical construction of Firestore references, and disciplined use of request-scoped context to ensure that cached or returned data is uniquely tied to the authenticated subject and the intended document path.
Firestore-Specific Remediation in Gorilla Mux — concrete code fixes
To prevent cache poisoning when using Gorilla Mux with Firestore, apply rigorous validation and canonicalization to any user-controlled data used to construct document references or queries. Always treat path parameters and query fields as untrusted and verify their format before using them in Firestore operations.
Use strict pattern validation for document identifiers and enforce ownership through Firestore security rules and application-level checks. For example, validate document IDs to ensure they contain only expected characters and do not contain path traversal sequences:
import (
"context"
"regexp"
"net/http"
"github.com/gorilla/mux"
"cloud.google.com/go/firestore"
)
var docIDRegex = regexp.MustCompile(`^[a-zA-Z0-9_-]{1,100}$`)
func safeDocHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
docID := vars["docID"]
if !docIDRegex.MatchString(docID) {
http.Error(w, "invalid document identifier", http.StatusBadRequest)
return
}
ctx := context.Background()
client, err := firestore.NewClient(ctx, "my-project")
if err != nil { /* handle error */ }
defer client.Close()
// Canonical reference: collection name is fixed, document ID is validated
docRef := client.Collection("user_items").Doc(docID)
snap, err := docRef.Get(ctx)
if err != nil {
// handle error, do not expose internal details
http.Error(w, "unable to retrieve document", http.StatusInternalServerError)
return
}
// Process snap.Data() safely
}
When queries involve user input, map the input to known-safe values or enumerated sets instead of raw strings. For example, if selecting a subcollection or field, use a switch to canonicalize the input rather than interpolating it directly into a Firestore reference:
func categoryHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
category := vars["category"]
var col string
switch category {
case "public", "private", "shared":
col = "public_items"
default:
http.Error(w, "unknown category", http.StatusBadRequest)
return
}
ctx := context.Background()
client, err := firestore.NewClient(ctx, "my-project")
if err != nil { /* handle error /}
defer client.Close()
// Canonical collection reference based on validated category
iter := client.Collection(col).Where("published", "==", true).Documents(ctx)
// process results
}
For multi-tenant scenarios, explicitly bind data access to the authenticated subject. Do not rely solely on Firestore security rules to prevent cross-user reads in application logic; validate that the requested document belongs to the current user:
func userItemHandler(w http.ResponseWriter, r *http.Request) {
userID := r.Context().Value("userID").(string) // from auth middleware
vars := mux.Vars(r)
itemID := vars["itemID"]
if !docIDRegex.MatchString(itemID) {
http.Error(w, "invalid item identifier", http.StatusBadRequest)
return
}
ctx := context.Background()
client, err := firestore.NewClient(ctx, "my-project")
if err != nil { /* handle error */ }
defer client.Close()
// Scoped reference that includes user ownership
docRef := client.Collection("users").Doc(userID).Collection("items").Doc(itemID)
snap, err := docRef.Get(ctx)
if err != nil {
http.Error(w, "not found", http.StatusNotFound)
return
}
// Return data safely
}
When caching responses, include the user identifier and validated document path in the cache key to avoid cross-user contamination. Avoid using raw query parameter values directly as cache keys:
cacheKey := fmt.Sprintf("user:%s:item:%s", userID, docID)
By combining strict validation, canonical references, and user-bound scoping, you reduce the risk of cache poisoning in Gorilla Mux routes backed by Firestore. These practices ensure that document references and query outcomes remain predictable and isolated per request context.