Api Key Exposure in Nestjs (Typescript)
Api Key Exposure in Nestjs with Typescript — how this specific combination creates or exposes the vulnerability
Api key exposure in a NestJS application written in TypeScript typically occurs when sensitive credentials are mishandled in source code, build artifacts, runtime configuration, or transport. Because TypeScript is compiled to JavaScript and NestJS applications often rely on environment-based configuration, developers may inadvertently embed keys in places that are exposed to clients, logs, or version control.
One common pattern is storing API keys in plain .env files and loading them via ConfigService. If these files are not excluded from version control or are committed with placeholder values, the keys can leak through repository exposure. In TypeScript, the use of string literals and imports can make it easier to accidentally expose keys in client-side bundles when modules are imported incorrectly or when server-side code is mistakenly bundled for the browser.
During runtime, NestJS applications can expose keys through error messages, logs, or HTTP response headers if exception filters or interceptors are not carefully designed. For example, a globally bound exception filter might include stack traces that reference configuration keys, especially when TypeScript source maps are enabled in production. The use of decorators like @Inject() or @Config() can inadvertently surface configuration values in serialized output if developers log request or response objects without sanitization.
Transport-layer exposure is another concern. Even when using HTTPS, improper certificate validation or mixed content scenarios can allow interception of API keys sent from NestJS clients. In TypeScript, the use of axios or fetch without strict validation of endpoints can lead to keys being sent to untrusted origins. This is especially risky when developers use string concatenation or template literals to build URLs, which can introduce injection or logging risks.
Build and deployment pipelines can also contribute to exposure. If TypeScript compilation outputs source maps or if environment variables are embedded into client-side bundles, API keys can be extracted from publicly accessible assets. In a NestJS context, this often happens when shared libraries or microservices incorrectly propagate keys through HTTP headers or query parameters without encryption or token-based alternatives.
Because middleBrick scans the unauthenticated attack surface and tests inputs, headers, and runtime behavior, it can detect whether API keys are exposed through error responses, overly verbose logging, or insecure transport configurations. The tool’s cross-referencing of OpenAPI specifications with runtime findings helps identify mismatches where documentation claims keys are hidden but runtime tests reveal otherwise.
Typescript-Specific Remediation in Nestjs — concrete code fixes
To mitigate api key exposure in NestJS with TypeScript, focus on strict separation between server-side and client-side code, secure configuration handling, and safe runtime practices. The following code examples demonstrate concrete, actionable fixes.
1. Secure configuration with ConfigModule and validation
Use NestJS’s built-in configuration system with Joi validation to ensure required keys are present and correctly typed. This prevents undefined or malformed values from propagating into your application.
// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { Joi, validate } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
validationSchema: Joi.object({
API_KEY: Joi.string().required(),
API_URL: Joi.string().uri().required(),
}),
validationOptions: { abortEarly: false },
}),
],
})
export class AppModule {}
2. Avoid exposing keys in client-side bundles
Ensure that modules importing configuration are only used on the server side. Never import ConfigService or environment-dependent modules into shared or client-side components. Use barrel files and dependency boundaries to enforce separation.
// src/config/api-key.config.ts — server-side only
import { Injectable } from '@nestjs/common'’;
import { ConfigService } from '@nestjs/config';
@Injectable()
export class ApiKeyService {
constructor(private readonly configService: ConfigService) {}
getApiKey(): string {
const key = this.configService.get('API_KEY');
if (!key) {
throw new Error('API_KEY is not defined');
}
return key;
}
}
3. Sanitize logs and error outputs
Custom exception filters should strip sensitive fields from error responses and avoid logging raw configuration values. Use structured logging with redaction.
// src/filters/sanitized-exceptions.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
@Catch()
export class SanitizedExceptionFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
// Do not expose stack or config in production
const status = exception instanceof HttpException ? exception.getStatus() : 500;
response.status(status).json({
statusCode: status,
message: 'An error occurred',
requestId: request.id,
});
}
}
4. Use HTTP interceptors to prevent accidental leakage
Client-side HTTP modules should strip sensitive headers and avoid logging outgoing requests that may contain keys.
// src/interceptors/api-key-safety.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { map } from 'rxjs/operators';
@Injectable()
export class ApiKeySafetyInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler) {
const request = context.switchToHttp().getRequest();
// Ensure client requests do not echo back server-side keys
request.headers.delete('x-api-key');
return next.handle().pipe(
map(data => data)
);
}
}
5. Enforce transport security and dependency hygiene
Use strict transport layer policies and avoid string-based URL assembly. Prefer typed configuration for endpoints.
// src/clients/external.client.ts
import { HttpService } from '@nestjs/axios';
import { Injectable } from '@nestjs/common';
import { map } from 'rxjs';
@Injectable()
export class ExternalClient {
constructor(private readonly httpService: HttpService) {}
fetchData() {
const url = this.httpService.getUri('/secure-endpoint'); // Use built-in URI resolution
return this.httpService.get(url).pipe(map(res => res.data));
}
}
These practices reduce the risk of api key exposure by enforcing compile-time safety, runtime hygiene, and clear boundaries between server and client code. middleBrick can validate that such configurations do not inadvertently leak through error responses or misconfigured endpoints.