HIGH broken access controlsailsfirestore

Broken Access Control in Sails with Firestore

Broken Access Control in Sails with Firestore — how this specific combination creates or exposes the vulnerability

Broken Access Control occurs when Sails enforces permissions incompletely or not at all, allowing a subject to bypass authorization checks and access or modify data that should be restricted. When Sails is used with Google Cloud Firestore as the persistence layer, the risk is twofold: the application layer may lack route/model policies, and Firestore security rules may be misconfigured, resulting in unauthenticated or over-privileged data access.

In a typical Sails app with Firestore, models are often mapped to Firestore collections using an ORM-like adapter (e.g., sails-hook-firestore or a custom service). If developers rely solely on Firestore rules to enforce ownership or role-based access, but do not enforce equivalent checks in Sails controllers, an attacker can invoke controller actions directly and potentially operate on records they should not see. Conversely, if Sails policies are used but Firestore rules are open (e.g., allowing read/write based only on authentication without verifying document ownership), data from one user can be accessed or overwritten by another.

Consider a Sails controller that retrieves user documents by ID without validating that the requesting user owns the document. An Insecure Direct Object Reference (IDOR) arises because the client-supplied ID is used to fetch a Firestore document without ownership verification. For example, an endpoint like /user/profile/:id might call UserProfile.find(req.params.id) in Sails, which under the hood queries Firestore with a permissive rule such as allow read: if request.auth != null;. An authenticated user can simply iterate over known IDs to access other users’ profiles, demonstrating BOLA/IDOR. This is a real-world OWASP API Top 10 A01:2023 broken access control pattern, and PCI-DSS and SOC2 require strict ownership checks at both application and storage layers.

Firestore’s rule syntax can inadvertently permit broad access if conditions are too permissive. For instance, a rule like allow write: if request.auth != null; grants any authenticated user write access to entire collections, enabling Privilege Escalation if Sails does not also enforce fine-grained role or scope checks. An attacker authenticated as a low-privilege user might exploit missing BFLA (Business Logic Functional Authorization) in Sails to call admin-only actions, and Firestore rules that do not scope writes to a user’s own document subcollection allow modification of other users’ data. Data Exposure can follow if sensitive fields (e.g., email, roles, PII) are readable due to missing field-level restrictions in rules and lack of runtime field filtering in Sails responses.

SSR and unsafe consumption patterns can exacerbate the issue. If Sails passes raw, unchecked client input into Firestore queries (e.g., using where clauses built from request parameters without validation), an attacker may probe for IDOR or exfiltrate data via crafted requests. Without rate limiting and input validation checks, automated enumeration becomes easier. Therefore, securing the Sails-to-Firestore path requires aligned protections: strict Sails policies, tightly scoped Firestore security rules, input validation, and runtime checks that ensure the subject owns or is authorized for the specific resource.

Firestore-Specific Remediation in Sails — concrete code fixes

To fix Broken Access Control when using Sails with Firestore, enforce ownership and role checks in both Sails application logic and Firestore security rules. Never rely on rules alone, and never rely on Sails policies alone; apply both layers consistently.

First, ensure every Firestore rule validates resource ownership using the authenticated UID and enforces least privilege. For a user-specific collection, scope reads and writes to the user’s document ID:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // User-specific documents: allow full access only to the owner
    match /userProfiles/{userId} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }
    // Shared data with admin-only writes
    match /sharedData/{docId} {
      allow read: if request.auth != null;
      allow write: if request.auth != null && request.auth.token.isAdmin == true;
    }
  }
}

Second, implement robust Sails policies that mirror these constraints. In api/policies/ownershipOrAdmin.js, check both resource ownership and administrative role before proceeding:

module.exports.ownershipOrAdmin = async function (req, res, next) {
  const model = req.options.model || req.model;
  const id = req.param('id');
  if (!id) return res.unauthorized('Missing resource ID.');

  const record = await model.findOne(id);
  if (!record) return res.notFound();

  const currentUser = req.user;
  // Assume record.userId stores the owning user’s ID and currentUser has uid
  if (!currentUser) return res.unauthorized('Authentication required.');
  if (currentUser.uid === record.userId || currentUser.isAdmin) return next();
  return res.forbidden('Access denied: insufficient permissions.');
};

Third, use Sails models or services to enforce field-level filtering and avoid returning sensitive fields inadvertently. When querying Firestore through a Sails service, scope queries to the user’s documents and explicitly select safe fields:

// api/services/ProfileService.js
module.exports = {
  async getProfile(userId, requestingUid) {
    if (userId !== requestingUid) {
      throw new Error('Access denied');
    }
    // Use the Firestore adapter to fetch only safe fields
    const profile = await UserProfile.findOne(userId).select('displayName avatar');
    return profile;
  },
};

In controllers, apply the policy and validate input before invoking services:

// api/controllers/ProfileController.js
module.exports = {
  async show(req, res) {
    const { id } = req.params;
    const userId = req.user.uid;
    if (id !== userId) {
      return res.forbidden('Cannot access other users profiles');
    }
    const profile = await ProfileService.getProfile(id, userId);
    return res.ok(profile);
  },
};

Finally, adopt continuous monitoring via the middleBrick Pro plan to detect regressions. With API security checks running in CI/CD, you can fail builds if a change introduces overly permissive Firestore rules or missing Sails policies, ensuring ongoing alignment between application logic and data access controls.

Frequently Asked Questions

How does middleBrick detect Broken Access Control in Sails with Firestore?
middleBrick runs unauthenticated scans with 12 parallel checks including Authentication, BOLA/IDOR, and Property Authorization. For Sails+Firestore, it maps findings to OWASP API Top 10 and checks both Sails policies and Firestore rules for missing ownership validation and overly permissive conditions.
Can middleBrick integrate into CI/CD to prevent regressions?
Yes. With the middleBrick Pro plan and GitHub Action, you can add API security checks to your CI/CD pipeline and fail builds if the security score drops below your configured threshold, helping prevent Broken Access Control from reaching production.