Bola Idor in Strapi with Cockroachdb
Bola Idor in Strapi with Cockroachdb — how this specific combination creates or exposes the vulnerability
BOLA (Broken Level of Authorization) / IDOR occurs when an API exposes one user’s resource through an identifier that should be restricted. Strapi, a headless CMS, often exposes REST or GraphQL endpoints where records are fetched by an integer or UUID path parameter without verifying ownership or tenant boundaries. When CockroachDB is used as the underlying database, the risk pattern is similar to other SQL databases, but specific behaviors in CockroachDB—such as its distributed SQL semantics and implicit type handling—can inadvertently affect how row-level filters are applied.
In Strapi, content types are typically queried via services or controllers that build a query like find({ where: { id: paramId } }). If the controller does not also enforce a tenant or user scope (for example, by missing a user_id or organization_id condition), an attacker can iterate through numeric IDs or guess UUIDs to access another user’s data. With CockroachDB, this becomes a BOLA issue if the query does not explicitly include a tenant column in the WHERE clause, because CockroachDB will still return a row if the primary key matches, regardless of the broader logical partition. Additionally, CockroachDB’s case-sensitive collation and UUID handling can lead to subtle mismatches: if Strapi sends a string UUID but the column is stored as an UUID type, CockroachDB may perform an implicit cast that bypasses expected index usage or leads to unexpected row matches in distributed joins, potentially exposing rows across partitions if the query is not precise.
Consider a Strapi endpoint GET /api/articles/:id that does not check which user or tenant owns the article. An attacker can change the ID and retrieve articles they should not see. If the underlying CockroachDB table includes a tenant_id column but Strapi’s query omits it, the database may still return the row because the primary key is globally unique. Furthermore, Cockroachdb’s serializable isolation can mask logic errors in development, making the vulnerability appear harmless in tests but exploitable in production under certain transaction patterns. The combination of Strapi’s default auto-generated endpoints and CockroachDB’s strong consistency can therefore create a BOLA surface that is not immediately obvious during initial development.
Cockroachdb-Specific Remediation in Strapi — concrete code fixes
To prevent BOLA/IDOR in Strapi when using CockroachDB, you must enforce tenant and ownership checks in every query and validate that the database schema supports scoping. Below are concrete, syntactically correct examples for Strapi custom controllers and raw queries that include CockroachDB-specific considerations.
1. Strapi Service with Tenant Scope
Ensure every find, count, and delete operation includes a tenant or user filter. For a content type Article, add a tenant_id field to the schema and always include it in queries.
// src/api/article/services/article.js
'use strict';
module.exports = {
async findByUserId(ctx) {
const { user } = ctx.state; // authenticated user from Strapi auth plugin
const { tenantId } = user; // assume tenantId is part of user payload
const articles = await strapi.entityService.findMany('api::article.article', {
filters: {
tenant_id: tenantId,
id: ctx.request.query.id ? { $eq: ctx.request.query.id } : { $null: true },
},
});
return articles;
},
};
2. Raw Knex Query with CockroachDB UUID and Type Safety
If you use custom controllers with raw Knex (CockroachDB driver), explicitly cast and bind parameters to avoid type confusion. CockroachDB’s wire protocol and Go-based driver handle UUIDs as strings in 'YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY' format; ensure Strapi passes them as strings and not as binary.
// src/api/article/controllers/article.js
'use strict';
module.exports = {
async getArticleById(ctx) {
const { id } = ctx.params;
const { user } = ctx.state;
const result = await strapi.db.connection('tenant_articles')
.where({ id, tenant_id: user.tenant_id })
.select('*')
.limit(1);
if (result.length === 0) {
return ctx.notFound();
}
ctx.body = result[0];
},
};
3. Middleware to Enforce Tenant Context
Add a global or route-level middleware that appends tenant filtering to every query. This ensures even dynamic finders include the tenant column.
// src/middlewares/tenant-filter/index.js
module.exports = (config, { strapi }) => {
return async (ctx, next) => {
const tenantId = ctx.request.header['x-tenant-id'];
if (!tenantId) {
return ctx.unauthorized('Tenant header missing');
}
// Attach tenantId for use in services/controllers
ctx.state.tenantId = tenantId;
await next();
};
};
4. Database Schema and Indexing in CockroachDB
Define the table with a composite primary key or secondary index that includes tenant_id. This ensures CockroachDB’s distributed planner uses the index for tenant-specific lookups and prevents full scans that could leak data across partitions.
-- CockroachDB SQL schema example
CREATE TABLE tenant_articles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
title STRING,
content STRING,
created_at TIMESTAMPTZ DEFAULT now(),
INDEX idx_tenant_id (tenant_id, id)
);
Related CWEs: bolaAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-250 | Execution with Unnecessary Privileges | HIGH |
| CWE-639 | Insecure Direct Object Reference | CRITICAL |
| CWE-732 | Incorrect Permission Assignment | HIGH |
Frequently Asked Questions
How can I test if my Strapi endpoints are vulnerable to BOLA when using CockroachDB?
middlebrick scan https://your-api.com/api/articles/1. Review the BOLA findings and verify that each response includes a tenant context check; ensure no row is returned when the tenant_id does not match.