Bola Idor in Nestjs with Api Keys
Bola Idor in Nestjs with Api Keys — how this combination creates or exposes the vulnerability
Broken Object Level Authorization (BOLA) occurs when an API fails to enforce proper ownership or scope checks between a subject (e.g., a user or service) and the object they are trying to access. Using API keys in a NestJS service can inadvertently create BOLA when keys are treated as a global identity without scoping each request to the key’s associated tenant or user context.
Consider a typical API key setup where a key is mapped to an organization or customer in a database. If a route like /organizations/:orgId/resource/:resourceId uses the API key only for authentication (verifying the key is valid) but does not ensure the authenticated key’s organization owns the requested orgId, an attacker can manipulate orgId or resourceId to access data belonging to another organization. This is BOLA: the API key proves identity but not authorization for the specific resource.
In NestJS, this often happens when controllers retrieve resources by ID without cross-checking the key’s metadata. For example, a service might fetch an organization by the ID provided in the URL, then pass it to a repository method. If the repository does not filter by the authenticated key’s organization, the request succeeds regardless of whether the key belongs to that organization. Attackers can enumerate IDs or guess predictable IDs (e.g., UUIDs or sequential integers) and observe differences in behavior or data returned, leading to data exposure.
Additionally, BOLA can be chained with other issues. If input validation is weak, an attacker might use path traversal or type confusion to escalate impact. Rate limiting that is scoped to the endpoint rather than to the key can allow a compromised key to be abused for enumeration at higher volumes. Because API keys are often long-lived credentials, a leaked key can facilitate sustained BOLA attacks across many endpoints if the backend does not enforce per-request ownership checks.
To detect this during scanning, middleBrick runs unauthenticated tests that probe predictable resource IDs while using a single API key, looking for unauthorized data access. It also checks whether authorization logic references the key’s associated scope (e.g., organization ID) on every request. These runtime checks complement spec analysis, where middleBrick resolves OpenAPI $ref chains to verify whether security schemes are applied consistently across path parameters and request bodies.
Api Keys-Specific Remediation in Nestjs — concrete code fixes
Remediation centers on ensuring that each authorized request validates ownership or scope tied to the API key, not just the key’s validity. Below are concrete patterns you can apply in NestJS.
1. Scoped key mapping with a guard
Create an API key guard that enforces scope before allowing access to a route. The guard resolves the key, attaches tenant or org metadata, and ensures that any resource-level operation includes a match between requested IDs and the key’s scope.
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { ApiKeyService } from './api-key.service';
@Injectable()
export class ApiKeyScopeGuard implements CanActivate {
constructor(private readonly apiKeyService: ApiKeyService) {}
async canActivate(context: ExecutionContext): Promise {
const request = context.switchToHttp().getRequest();
const key = request.headers['x-api-key'];
if (!key) return false;
const scopedKey = await this.apiKeyService.validateAndScope(key);
if (!scopedKey) return false;
// Attach scoped identity for downstream use
request.scopedKey = scopedKey;
return true;
}
}
2. Enforce scope in the service layer
Never trust URL IDs alone. Your service method should require the scoped key and use it to filter queries. Here is a service that fetches an organization resource only when the scoped key’s organization matches the requested orgId.
import { Injectable, NotFoundException } from '@nestjs/common';
import { Repository } from 'typeorm';
import { Organization } from './organization.entity';
interface ScopedKey {
orgId: string;
permissions: string[];
}
@Injectable()
export class ResourceService {
constructor(
@InjectRepository(Organization)
private readonly orgRepo: Repository,
private readonly scopedKey: ScopedKey,
) {}
async getResource(orgId: string, resourceId: string) {
if (orgId !== this.scopedKey.orgId) {
throw new NotFoundException('Resource not found');
}
const resource = await this.orgRepo
.createQueryBuilder('org')
.where('org.id = :orgId', { orgId })
.andWhere('org.resources @> :resources', { resources: [resourceId] })
.getOne();
if (!resource) {
throw new NotFoundException('Resource not found');
}
return resource;
}
}
3. Apply the guard globally or per-controller
Register the guard globally or on specific controllers to ensure consistent enforcement. Combine with built-in validation pipes for IDs to reduce injection surface.
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_GUARD,
useClass: ApiKeyScopeGuard,
},
],
})
export class AuthModule {}
4. Use parameterized queries and avoid concatenation
Always use parameterized queries or query builders. Do not concatenate IDs into raw SQL or command strings, even after scope checks, to prevent injection that could bypass intended scoping.
5. Rotate keys and monitor usage
While not a code fix, operational practices reduce exposure. Rotate API keys periodically and log access with key identifiers to detect anomalous patterns that may indicate BOLA probing.
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 |