Beast Attack in Nestjs with Api Keys
Beast Attack in Nestjs with Api Keys — how this specific combination creates or exposes the vulnerability
A Beast Attack (short for “Bypassing Explicit Authorization via Schema Tampering”) in a NestJS API that uses API keys can occur when authorization checks are performed only at the handler level and not on each underlying data-access or service layer. If API keys are accepted in headers or query parameters and mapped to a user or scope in middleware, but routes or controllers then rely on implicit trust or incomplete property-level authorization, an attacker can manipulate parameter names (e.g., swapping IDs or keys) to access resources they should not see. This is commonly seen where route parameters like userId or resourceId are used to fetch records without re-validating that the requesting API key is allowed to access that specific identifier.
In NestJS, this often happens when developers use guards and interceptors to validate API keys for authentication but skip consistent authorization checks across services and DTOs. For example, an endpoint like GET /users/:userId/profile might verify the API key in an Authorization header, but if the handler directly uses userId from the params without confirming the key maps to that user, a Beast Attack can occur through parameter substitution or IDOR-like manipulation. The attack surface is amplified when OpenAPI specs expose nested or inferred parameters and the runtime behavior does not re-check scopes or ownership for each nested resource.
Because middleBrick tests unauthenticated attack surfaces and includes BOLA/IDOR and BFLA/Privilege Escalation checks, it can surface patterns where API-key-protected endpoints inadvertently leak data across tenants or roles. When combined with property-level authorization gaps, Beast Attack patterns allow reading or inferring other users’ data by simply changing identifiers in the request. The scanner highlights these risks alongside input validation and authorization checks, emphasizing that authentication (API key presence) is not sufficient without explicit per-request authorization.
Api Keys-Specific Remediation in Nestjs — concrete code fixes
Remediation focuses on ensuring that every request that uses API keys re-validates scope and ownership at the point of data access, and that keys are handled through secure extraction and mapping patterns. Avoid placing trust in route parameters alone; instead, derive the allowed resource identifiers from the authenticated context provided by the API key.
- Extract the API key centrally and map it to a verified context before controllers run.
- Use a combination of guards for authentication and explicit authorization checks in services or data access layers.
- Never rely on client-supplied identifiers to fetch sensitive records without verifying that the API key has access to that identifier.
| Approach | When to use | Example pattern |
|---|---|---|
| Guard-based auth + service-level authz | Most endpoints with tenant or user scoping | Check mapping in guard, re-validate in service |
| Request-scoped context | Complex multi-service calls per request | Build a context object early and pass it through layers |
import { Injectable, NestMiddleware, ExecutionContext, BadRequestException } from '@nestjs/common';
import { Observable } from 'rxjs';
interface ApiKeyContext {
key: string;
allowedScopes: string[];
tenantId: string;
}
@Injectable()
export class ApiKeyMiddleware implements NestMiddleware {
constructor() {}
use(req: Request, res: Response, next: () => void): void {
const key = req.headers['x-api-key'] || req.query['api_key'];
if (!key) {
res.status(401).json({ error: 'API key missing' });
return;
}
// In practice, look up key from a secure store and validate scopes/tenant
const context: ApiKeyContext = {
key: key as string,
allowedScopes: this.extractScopes(key as string),
tenantId: this.extractTenant(key as string),
};
(req as any).apiKeyCtx = context;
next();
}
private extractScopes(key: string): string[] {
// Map key to scopes; keep logic out of middleware for testability
return ['data:read', 'data:write'];
}
private extractTenant(key: string): string {
// Map key to tenant; ensure no collisions across customers
return 'tenant-abc';
}
}
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { ApiKeyContext } from './api-key.middleware';
@Injectable()
export class ApiKeyAuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const req = context.switchToHttp().getRequest();
const userCtx: ApiKeyContext = (req as any).apiKeyCtx;
if (!userCtx) return false;
// Additional checks like scope or tenant validation can go here
return true;
}
}
import { Injectable } from '@nestjs/common';
export type ProfileContext = {
userId: string;
tenantId: string;
};
@Injectable()
export class ProfileService {
async getProfile(context: ProfileContext, requestedUserId: string) {
if (context.userId !== requestedUserId) {
throw new ForbiddenException('Access denied to this profile');
}
// Proceed with data fetch using context.tenantId for scoping
return this.fetchProfileById(requestedUserId, context.tenantId);
}
private async fetchProfileById(userId: string, tenantId: string) {
// Ensure tenantId is used in the query to enforce row-level security
return { id: userId, tenantId, name: 'Sample' };
}
}
In the examples above, the API key is used to establish a context early in the request lifecycle. Controllers then receive a strongly-typed context and must explicitly validate that the requester is allowed to act on the supplied identifiers. This prevents Beast Attack patterns where changing a route parameter would grant access to another tenant or user’s data. middleBrick can help verify that such checks exist in both spec definitions and runtime behavior, reducing risks from improper authorization and parameter handling.