HIGH bola idorsailsapi keys

Bola Idor in Sails with Api Keys

Bola Idor in Sails with Api Keys — how this combination creates or exposes the vulnerability

Broken Object Level Authorization (BOLA) occurs when an API fails to enforce authorization checks between a user and a specific object identifier (ID). In Sails.js applications that rely on API keys for authentication, BOLA can emerge when the key identifies the application or integration but does not restrict access to individual records a user is permitted to manage. For example, an endpoint like GET /user/:userId/profile might validate the presence of an API key in a header but never confirm that the authenticated key’s associated principal owns the userId in the URL. This gap allows an attacker to iterate over plausible IDs and read or modify other users’ data.

Consider a Sails controller that retrieves a user profile using a service without a proper ownership check:

// api/controllers/ProfileController.js
module.exports = {
  async show(req, res) {
    const userId = req.param('userId');
    const profile = await UserProfile.findOne(userId);
    return res.ok(profile);
  }
};

If the request includes an API key that identifies an integration with broad permissions, and the controller does not ensure the found UserProfile belongs to the key’s authorized subject, the endpoint is vulnerable to BOLA. Attackers can change userId to access profiles they should not see or edit. This risk is compounded when API keys have long lifetimes or are shared across services, increasing the blast radius of a leaked key.

Sails’ policy system and model hooks can inadvertently enable BOLA when developers rely solely on global policies or implicit associations. For instance, a policy that only checks for a valid session or API key but does not scope the model query to the authenticated actor leaves a path open. An attacker making direct HTTP requests can probe endpoints with modified IDs because the authorization logic does not tie the key to a specific resource ownership chain.

Real-world attack patterns mirror this: enumeration via sequential IDs, tampering with referral IDs in nested resources, or leveraging weakly enforced relationships in REST or GraphQL-like query patterns. Common CWE entries such as CWE-639 (Authorization Bypass Through User-Controlled Key) align with these misconfigurations. Because API keys are often treated as static secrets, developers may overlook the need to bind them to a dynamic access control context at the record level.

Api Keys-Specific Remediation in Sails — concrete code fixes

Remediation centers on ensuring that every data retrieval or mutation validates ownership or explicit permission between the API key’s scope and the requested object. Below are concrete patterns you can apply in Sails controllers and services.

1. Bind API key context to the query

Instead of fetching a record by ID alone, include a scope that ties the record to the key’s allowed resources. If your API key maps to a team or tenant, enforce that the record belongs to that team.

// api/controllers/ProfileController.js
module.exports = {
  async show(req, res) {
    const userId = req.param('userId');
    const apiKey = req.apiKey; // assume populated by a hook
    if (!apiKey) {
      return res.unauthorized('Missing API key');
    }
    // Ensure the profile belongs to the allowed subject for this key
    const profile = await UserProfile.findOne({
      id: userId,
      teamId: apiKey.teamId // scope by team or other access boundary
    });
    if (!profile) {
      return res.notFound();
    }
    return res.ok(profile);
  }
};

2. Use a service layer to centralize authorization

Create a service that checks whether the key’s permissions include the target object. This keeps controllers thin and reduces the chance of missing checks.

// api/services/authorization/ProfilePolicy.js
module.exports = {
  async canAccessProfile(apiKey, profileId) {
    const profile = await UserProfile.findOne(profileId);
    if (!profile) return false;
    return profile.teamId === apiKey.teamId;
  },
  async getProfileSafely(apiKey, profileId) {
    const allowed = await this.canAccessProfile(apiKey, profileId);
    if (!allowed) throw new Error('Unauthorized');
    return UserProfile.findOne(profileId);
  }
};

// api/controllers/ProfileController.js
module.exports = {
  async show(req, res) {
    const apiKey = req.apiKey;
    const profileId = req.param('userId');
    try {
      const profile = await ProfilePolicy.getProfileSafely(apiKey, profileId);
      return res.ok(profile);
    } catch (err) {
      return res.forbidden('You do not have access to this profile');
    }
  }
};

3. Enforce ownership in model lifecycle callbacks cautiously

While model policies and lifecycle callbacks can help, avoid embedding key-based scoping there unless you guarantee the key is available and the callback’s query constraints are explicit. Prefer controller or service-level checks for clarity.

4. Rotate and scope API keys

Design API keys to carry minimal scope (e.g., teamId, allowed endpoints). Rotate keys regularly and avoid using a single key for cross-user operations. In Sails, you can validate key scope early in the request lifecycle using a hook:

// api/hooks/api-key-auth/index.js
module.exports = function attachApiKey(req, res, proceed) {
  const key = req.headers['x-api-key'];
  if (!key) return res.unauthorized('API key required');
  ApiKey.findOne({ key }).populate('team').exec((err, record) => {
    if (err || !record || record.revoked) {
      return res.unauthorized('Invalid API key');
    }
    req.apiKey = record; // attach for downstream use
    proceed();
  });
};

By combining these patterns—scoped queries, policy services, and key scoping—you mitigate BOLA in Sails applications that depend on API keys without introducing automatic fixing or assumed framework behavior.

Related CWEs: bolaAuthorization

CWE IDNameSeverity
CWE-250Execution with Unnecessary Privileges HIGH
CWE-639Insecure Direct Object Reference CRITICAL
CWE-732Incorrect Permission Assignment HIGH

Frequently Asked Questions

How does API key scoping help prevent BOLA in Sails?
Scoping binds each API key to a specific boundary such as a teamId. By including that scope in every model query (e.g., teamId: apiKey.teamId), you ensure that users can only access records explicitly associated with their key’s permissions, closing the enumeration path.
Can Sails policies alone stop BOLA when using API keys?
Policies that only validate the presence of a key are insufficient because they typically lack record-level context. You must enforce ownership or explicit permission checks within the controller or a dedicated policy service to prevent BOLA.