Api Key Exposure in Nestjs with Firestore
Api Key Exposure in Nestjs with Firestore — how this specific combination creates or exposes the vulnerability
When a NestJS application uses Google Cloud Firestore, developers sometimes embed service account keys or API-scoped credentials in source code, configuration files, or build artifacts. These keys grant access to Firestore resources and are often long-lived, making them high-value targets. If an API endpoint in NestJS constructs Firestore clients using these embedded keys and exposes debug routes, logs, or error messages, the keys can leak through response data or stack traces. Additionally, misconfigured HTTP security headers or overly permissive CORS rules in the NestJS layer can expose API routes that return sensitive data to unauthorized origins, indirectly revealing authentication material in transit. In a black-box scan, middleBrick tests the unauthenticated attack surface and can detect exposed endpoints that return key identifiers, overly descriptive errors, or metadata that facilitates credential harvesting.
Another vector specific to the NestJS + Firestore combination is insecure server-side SDK initialization. If the Firestore SDK is initialized with a service account key at module load time and that key is derived from environment variables that are inadvertently shared with the client (for example, through GraphQL schema introspection, GraphQL Playground left enabled in dev, or REST endpoints that return environment metadata), the API becomes a direct leak channel. Firestore security rules are enforced per request, but if the backend itself uses a privileged key to sign transactions or perform admin reads, those operations bypass rules entirely. middleBrick’s authentication and data exposure checks look for endpoints that return secrets or sensitive configuration snippets and flag findings that align with OWASP API Top 10 Broken Object Level Authorization and Sensitive Data Exposure.
Runtime integrations between NestJS and Firestore can also expose keys via dependency chains. For example, if an endpoint uses a Firestore document to store user preferences that reference other service accounts or keys, and the endpoint serializes entire Firestore documents into responses, keys stored as document fields may be returned to clients. This pattern violates least privilege and can lead to lateral movement if the exposed key has broader permissions than intended. The LLM/AI security module in middleBrick specifically checks for system prompt leakage and output scanning; while focused on AI endpoints, the underlying principle applies to any API output that should not include credentials.
middleBrick’s inventory management and property authorization checks help surface where Firestore document paths or collection names are reflected in API responses, which can inadvertently disclose logical mappings that aid an attacker. Combined with rate limiting weaknesses, an attacker could probe these endpoints to enumerate valid document IDs and infer key storage patterns. Because Firestore rules can be complex, misconfigured rules plus verbose NestJS error handling can reveal which fields exist and where keys might be stored. The scanner runs parallel checks across authentication, input validation, and data exposure to highlight risky endpoint behaviors without requiring access credentials.
In practice, the simplest exposure occurs when developers copy service account JSON keys into environment variables that are shared between NestJS and frontend builds or container images that are publicly accessible. If a CI/CD pipeline artifact or Docker image layer contains these keys and the API serves error pages that include process environment dumps, the keys are effectively public. middleBrick’s scanning of unauthenticated attack surfaces is designed to detect such misconfigurations by analyzing responses and metadata returned by the API endpoints themselves.
Firestore-Specific Remediation in Nestjs — concrete code fixes
To mitigate exposure, initialize the Firestore client outside of request handlers and avoid passing service account keys through API responses. Use Application Default Credentials (ADC) in production environments, and restrict key usage to backend-only contexts. In NestJS, configure the Firestore provider to use a restricted service account with minimal permissions and ensure that no credentials are serialized into responses or logs.
import { Injectable } from '@nestjs/common';
import { initializeApp, cert } from 'firebase-admin/app';
import { getFirestore, FieldValue } from 'firebase-admin/firestore';
@Injectable()
export class FirestoreService {
private firestore;
constructor() {
// Use a restricted service account with least privilege
initializeApp({
credential: cert({
projectId: process.env.FIREBASE_PROJECT_ID,
clientEmail: process.env.FIRESTORE_CLIENT_EMAIL,
privateKey: process.env.FIRESTORE_PRIVATE_KEY.replace(/\\n/g, '\n'),
}),
});
this.firestore = getFirestore();
}
async getUserSettings(userId: string) {
const doc = await this.firestore.collection('userSettings').doc(userId).get();
if (!doc.exists) {
return null;
}
// Return only safe, non-sensitive fields
const data = doc.data();
return {
theme: data?.theme || 'light',
notificationsEnabled: data?.notificationsEnabled ?? true,
};
}
async logEvent(eventType: string, metadata: Record) {
await this.firestore.collection('events').add({
eventType,
metadata,
createdAt: FieldValue.serverTimestamp(),
});
}
}
Ensure that Firestore security rules deny read/write access to keys or service account metadata stored as documents. Avoid storing credentials as fields in user-accessible documents. If you must store sensitive configuration, use Google Secret Manager and retrieve values at runtime, never through the API layer that clients can invoke.
import { Injectable } from '@nestjs/common';
import { getFirestore, doc, getDoc } from 'firebase-admin/firestore';
import { SecretManagerServiceClient } from '@google-cloud/secret-manager';
@Injectable()
export class ConfigService {
private client = new SecretManagerServiceClient();
async getFirestoreApiKey(projectId: string, secretId: string) {
const [version] = await this.client.accessSecretVersion({
name: `projects/${projectId}/secrets/${secretId}/versions/latest`,
});
return version.payload?.data?.toString() ?? '';
}
async getRestrictedDoc(collectionName: string, docId: string) {
const db = getFirestore();
const snapshot = await getDoc(doc(db, collectionName, docId));
if (!snapshot.exists()) {
return null;
}
// Explicitly pick safe fields to avoid accidental exposure
const data = snapshot.data();
return {
name: data?.name,
status: data?.status,
};
}
}
In your NestJS controllers, validate and sanitize all inputs to prevent injection or path traversal that could expose document structures containing keys. Use class-validator and class-sanitizer to enforce strict schemas, and avoid returning entire Firestore documents. Log errors without stack traces or environment details, and ensure HTTP security headers prevent information leakage through browser-side scripts.
import { Controller, Get, Param, Query, HttpException, HttpStatus } from '@nestjs/common';
import { FirestoreService } from './firestore.service';
import { validate } from 'class-validator';
@Controller('settings')
export class SettingsController {
constructor(private firestoreService: FirestoreService) {}
@Get(':userId')
async getUserSettings(@Param('userId', new ParseIntPipe({ min: 1 })) userId: number) {
const settings = await this.firestoreService.getUserSettings(String(userId));
if (!settings) {
throw new HttpException('Not found', HttpStatus.NOT_FOUND);
}
return settings;
}
}
middleBrick’s GitHub Action can be added to CI/CD pipelines to fail builds if risk scores drop below your defined threshold, preventing deployments that expose sensitive configuration. Use the CLI to scan API definitions and runtime behavior, and leverage the dashboard to track security scores over time. The MCP Server allows you to scan APIs directly from your IDE, helping catch misconfigurations before code is committed.