Zip Slip in Adonisjs with Firestore
Zip Slip in Adonisjs with Firestore — how this specific combination creates or exposes the vulnerability
Zip Slip is a path traversal pattern where an attacker-supplied archive contains files with crafted paths that escape the intended extraction directory. In an AdonisJS application that interacts with Google Cloud Firestore, the risk arises when file metadata from an untrusted source is used to construct local filesystem paths or to name temporary resources before they are uploaded to Firestore.
Consider an endpoint that accepts a ZIP upload, extracts it, and then writes extracted file contents into Firestore as blobs. If the archive includes entries such as ../../../etc/passwd, and the extraction routine joins these paths without normalization and validation, the application can write outside the intended directory. Even if the immediate impact is local filesystem read/write, an attacker may leverage this to overwrite application code or configuration files that affect how the AdonisJS app initializes its Firestore client, potentially leading to unauthorized data access or injection of malicious environment variables.
Another scenario involves dynamic file naming in Firestore. If an application uses user-controlled fields from the ZIP (e.g., file names or directory structures) to build document IDs or Firestore collection/group paths without strict allowlisting, it may inadvertently enable path-like traversal in the document hierarchy. While Firestore itself does not interpret ../ as a filesystem traversal, an application that mirrors ZIP internal paths into Firestore document paths can expose sensitive data or create inconsistent authorization checks. For example, using a raw filename to form a document reference may bypass intended tenant or user isolation, effectively a logical IDOR that mirrors Zip Slip’s intent to access or overwrite unauthorized resources.
The combination of AdonisJS handling multipart/form-data uploads and Firestore SDK calls increases the attack surface if any intermediate path or filename is trusted. The framework’s validators help, but they must be applied rigorously to reject paths containing .. segments or absolute paths and to enforce strict filename normalization before any filesystem operation or Firestore write occurs.
Firestore-Specific Remediation in AdonisJS — concrete code fixes
Remediation centers on strict input validation, path normalization, and avoiding direct use of user input for filesystem or Firestore paths. Below are concrete, safe patterns for AdonisJS combined with Firestore.
1. Validate and sanitize ZIP entries before extraction
Use a library that prevents path traversal during extraction. For example, with the adm-zip package, ensure each entry is sanitized:
import { join, normalize } from 'path';
import { ensureDirSync } from 'fs-extra';
import Zip from 'adm-zip';
export async function safeExtractZip(buffer: Buffer, destination: string) {
const zip = new Zip(buffer);
const zipEntries = zip.getEntries();
for (const entry of zipEntries) {
// Normalize and resolve the entry path
const normalized = normalize(entry.entryName);
if (normalized.startsWith('..') || normalized.includes('\0')) {
throw new Error('Invalid zip entry path: ' + entry.entryName);
}
const target = normalize(join(destination, entry.entryName));
if (!target.startsWith(destination)) {
throw new Error('Zip Slip detected: entry escapes destination');
}
ensureDirSync(normalize(join(destination, entry.entryName.replace(/\/[^/]*$/, ''))));
// entry.extract({ path: destination }); // use a safe extraction method
}
}
2. Use Firestore with sanitized, allowlisted identifiers
When storing extracted file metadata in Firestore, derive document IDs from a server-generated UUID rather than raw filenames. If you must preserve filenames, sanitize and allowlist them:
import { db } from '~/utils/firestore';
import { v4 as uuidv4 } from 'uuid';
export async function storeFileMetadata(formData: any, bucketName: string) {
const files = formData.files; // assume validated array of file objects
for (const file of files) {
// Sanitize filename: remove path segments and control characters
const safeName = file.filename.replace(/^.*[\\/]/, '').replace(/[^a-zA-Z0-9._-]/g, '_');
const docId = safeName || uuidv4();
const docRef = db.collection(bucketName).doc(docId);
await docRef.set({
originalName: file.originalname,
safeName,
size: file.size,
uploadedAt: new Date(),
});
}
}
3. Enforce strict validation in AdonisJS routes
Use AdonisJS schema validation to reject suspicious paths and enforce file type rules:
import { schema } from '@ioc:Adonis/Core/Validator';
const fileSchema = schema.create({
files: schema.array.optional([
schema.object({
size: schema.number(),
name: schema.string({}, [rules.maxLength(255)]),
})
]),
});
export const uploadSchema = schema.create({
body: schema.object({
files: schema.array.optional([
schema.object({
filename: schema.string({}, [rules.maxLength(255), rules.escape])
})
])
})
});
4. Isolate Firestore credentials and avoid dynamic collection paths
Do not construct collection or document paths from user input. If multi-tenancy is required, use a fixed prefix or allowlisted tenant ID:
const tenantId = 'fixed_tenant_123'; // from auth context, not user input
const userCollection = db.collection('tenants').doc(tenantId).collection('uploads');
await userCollection.doc(uuidv4()).set({ data: 'safe' });