Bola Idor in Nestjs with Mutual Tls
Bola Idor in Nestjs with Mutual Tls — how this specific combination creates or exposes the vulnerability
Broken Object Level Authorization (BOLA) occurs when an API fails to enforce proper ownership or authorization checks between resources, allowing one user to act on another’s data. In a NestJS application using mutual TLS (mTLS), the presence of client certificates can create a false sense of strong identity while missing fine-grained authorization at the resource level. mTLS authenticates the client to the server by validating a client certificate, but it does not by itself enforce that a given authenticated principal is allowed to access or modify a specific resource owned by another principal.
Consider a typical NestJS setup where mTLS is used for client authentication: the framework validates the client certificate and maps it to a user identity. If route handlers then use only the client certificate identity (e.g., a subject or serial from the certificate) to authorize requests, BOLA can emerge. For example, an endpoint like DELETE /users/:userId may authenticate the request via mTLS and confirm the certificate maps to a user, but if the handler does not verify that the authenticated user is the owner of the :userId path parameter, an attacker who knows another user’s ID can delete or read that user’s data. Because mTLS operates at the transport layer, developers may mistakenly equate certificate validity with object-level permissions, leaving object ownership and tenant boundaries unchecked in application code.
When OpenAPI specs are analyzed, definitions and $ref resolution can reveal endpoints where path or query parameters represent user-owned resources, but if runtime checks are absent, the unauthenticated attack surface tested by middleBrick will flag BOLA findings. Attack patterns such as Insecure Direct Object References (IDOR) commonly exploit this gap. The combination of mTLS and NestJS can therefore expose BOLA when identity from the certificate is used as a coarse proxy for authorization, and when authorization logic does not compare the authenticated identity against the targeted resource’s ownership or tenant.
Mutual Tls-Specific Remediation in Nestjs — concrete code fixes
Remediation centers on ensuring that mTLS-derived identity is explicitly checked against resource ownership and tenant context in every handler. Do not rely on mTLS alone to enforce object-level permissions. Implement authorization logic that compares the authenticated principal (derived from the client certificate) with the resource’s owning principal before performing any operation.
Example of a secure NestJS guard and service pattern:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class ResourceOwnershipGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
const user = request.user; // set by mTLS/NestJS auth guard, containing identity from client cert
const resourceId = request.params.id; // e.g., user ID in path
const resource = this.userService.findOne(resourceId);
if (!resource) return false;
// Ensure the authenticated user owns the resource
return resource.ownerId === user.sub;
}
}
Example mTLS setup in NestJS using @nestjs/microservices patterns and a custom AuthGuard that extracts identity from the verified certificate:
import { Injectable, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
export class MtlsAuthGuard extends AuthGuard('custom-mtls') {
async canActivate(context: ExecutionContext) {
await super.canActivate(context);
const ctx = context.switchToHttp();
const request = ctx.getRequest();
// Assuming the MTLS verification layer sets request.clientCert
const cert = request.clientCert;
if (!cert) throw new UnauthorizedException('Client certificate required');
// Map certificate fields to a user identity
request.user = { sub: cert.subject.CN, tenantId: cert.tenantId };
return true;
}
}
Use the guard on controllers and combine with a policy-based check in service methods to enforce BOLA consistently:
import { Injectable } from '@nestjs/common';
@Injectable()
export class UserService {
deleteUser(currentUser: { sub: string; tenantId: string }, userId: string) {
if (currentUser.sub !== userId) {
throw new Error('Unauthorized: cannot delete another user');
}
// proceed with deletion
}
}
In the dashboard, track per-endpoint authorization coverage; with the Pro plan you can enable continuous monitoring to detect regressions where mTLS authentication is present but object-level checks are missing. The CLI can be integrated into scripts to fail builds if findings include BOLA for endpoints that involve user-owned resources, and the GitHub Action can gate merges when risk scores exceed your chosen thresholds.
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 |