HIGH bola idorstrapiapi keys

Bola Idor in Strapi with Api Keys

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

Broken Object Level Authorization (BOLA) occurs when an API exposes one user’s resource through another user’s request without proper ownership checks. In Strapi, content types such as Post or Order often have record IDs that are predictable integers. If authorization relies only on an API key and does not enforce tenant or ownership boundaries, an attacker can iterate IDs and access data they should not see.

When API keys are used in Strapi, they typically act as a shared secret identifying an integration or a role rather than a user context. If a key is compromised or overly permissive, and endpoints do not validate that the authenticated key is allowed to access the specific resource ID, BOLA arises. For example, an endpoint like /api/orders/:id that checks only for a valid API key may return order details for any order ID, enabling an attacker to enumerate orders from other customers.

Strapi’s permission system can inadvertently allow BOLA when custom policies or roles grant broad read access without scoping to the requesting actor. If API keys are mapped to roles that bypass ownership checks, the combination of predictable IDs and weak scoping leads to vertical or horizontal privilege escalation across records. This is especially risky when sensitive data fields are returned without additional field-level authorization, exposing PII or financial information.

Consider an unauthenticated or weakly authenticated endpoint that returns a user’s profile using a query like strapi.db.query('api::user.user').findOne({ where: { id: paramId } }). If the caller provides only an API key and the paramId is user-controlled without verifying that the profile belongs to the key’s associated tenant or scope, the API leaks one user’s data to another. Real-world attack patterns include changing numeric IDs in authenticated requests to harvest data across accounts.

To detect this during a scan, middleBrick tests endpoints that use API keys without enforcing resource-level constraints, checking whether records belonging to other principals are returned. The LLM/AI Security checks also probe for system prompt leakage that might occur if error messages expose stack traces or internal logic when invalid IDs are supplied with a valid key.

Api Keys-Specific Remediation in Strapi — concrete code fixes

Remediation centers on ensuring that every read or write operation validates ownership or tenant scope, even when an API key is present. Avoid relying on API keys alone for object-level decisions. Instead, scope queries to the principal associated with the key, and enforce strict field-level permissions.

Example of a vulnerable query that contributes to BOLA:

// Vulnerable: no ownership check, only API key validation
strapi.db.query('api::product.product').findOne({
  where: { id: request.query.id }
});

Secure alternative with tenant/user scoping:

// Secure: scope by tenantId associated with the API key
strapi.db.query('api::product.product').findOne({
  where: {
    id: request.query.id,
    tenantId: request.state.tenantId // derived from API key metadata
  }
});

In Strapi v4, you can use a policy to enforce ownership before controller actions. For example, create a policy that enriches the request context with the allowed scope derived from the API key:

// policies/api-key-scope.js
module.exports = async (ctx, next) => {
  const apiKey = ctx.request.header['x-api-key'];
  const keyRecord = await strapi.entityService.findOne('api::apikey.apikey', {
    where: { key: apiKey },
    populate: ['scopes']
  });
  if (!keyRecord) {
    ctx.unauthorized('Invalid API key');
    return;
  }
  // Attach allowed tenant or user IDs to context for later use in controllers
  ctx.state.allowedTenantId = keyRecord.tenantId;
  await next();
};

Then in the controller, use the scoped query:

// controllers/order.js
async findOne(ctx) {
  const { id } = ctx.params;
  const tenantId = ctx.state.allowedTenantId;
  const entry = await strapi.db.query('api::order.order').findOne({
    where: { id, tenantId }
  });
  if (!entry) {
    return ctx.notFound();
  }
  // Optionally filter sensitive fields
  const { password, internalNotes, ...safeEntry } = entry;
  ctx.body = safeEntry;
}

For list endpoints, enforce the same scoping and pagination to reduce enumeration risks:

// controllers/product.js
async find(ctx) {
  const tenantId = ctx.state.allowedTenantId;
  const entities = await strapi.db.query('api::product.product').findMany({
    where: { tenantId },
    pagination: ctx.pagination
  });
  ctx.body = entities;
}

Rotate and audit API keys regularly, limit key scopes to least privilege, and avoid returning internal IDs or sensitive fields unless necessary. middleBrick’s scans validate that endpoints using API keys enforce resource-level checks and do not expose records across tenants.

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

Can API keys alone prevent BOLA if IDs are predictable?
No. API keys identify integration or role but do not enforce per-user or per-tenant object ownership. Without scoping queries to the principal associated with the key, predictable IDs remain vulnerable to enumeration.
How does middleBrick detect BOLA in endpoints using API keys?
middleBrick tests access to other users’ or tenants’ records using different IDs while presenting the same API key, and checks whether sensitive data is returned, indicating missing ownership checks.