Zip Slip in Feathersjs with Firestore
Zip Slip in Feathersjs with Firestore — how this specific combination creates or exposes the vulnerability
Zip Slip is a path traversal vulnerability that occurs when a server extracts or constructs file paths using user-supplied input without proper validation. In a Feathersjs application that uses Google Cloud Firestore, the risk arises when file-related operations (for example, serving user uploads or importing data) build destination paths by concatenating a base directory with a filename provided by the client. If the filename contains sequences like ../, an attacker can traverse outside the intended directory and overwrite arbitrary files on the host filesystem. This becomes particularly dangerous when the Feathersjs service runs with elevated permissions or when the runtime environment exposes sensitive files.
Even though Firestore itself is a managed NoSQL database and does not directly handle filesystem paths, the vulnerability manifests in the application layer. Feathersjs services often combine Firestore for metadata storage with local or cloud storage for files. If the service uses user-controlled input to name files or directories—such as storing uploaded images or export files—and then resolves those paths insecurely, an attacker can manipulate the path traversal to escape the intended sandbox. For instance, a malicious filename like ../../../etc/passwd could be appended to a base path, causing the server to write or read outside the allowed directory. The Feathersjs hook architecture may inadvertently pass unchecked file paths to filesystem utilities, enabling the exploit chain.
An attacker might also leverage insecure default configurations or missing input sanitization in Feathersjs hooks that interact with storage plugins. If the service validates data against Firestore schemas but does not enforce strict path canonicalization before writing files, the Zip Slip vector remains open. The presence of Firestore does not prevent filesystem traversal; it only shifts the focus to how the application bridges database records with file system operations. Therefore, developers must treat file paths as untrusted input, regardless of the database used.
Firestore-Specific Remediation in Feathersjs — concrete code fixes
To mitigate Zip Slip in a Feathersjs application that uses Firestore, you must validate and sanitize any user-controlled input used in file paths before interacting with the filesystem. Always resolve paths using secure utilities that prevent directory traversal, and store only safe, canonical paths in Firestore documents.
Secure path resolution and canonicalization
Use Node.js path.resolve() and path.normalize() to construct absolute paths, and ensure the resulting path remains within the intended directory by prefix-checking against a base directory. Avoid simple string concatenation.
import path from 'path';
const baseDir = '/safe/upload/dir';
function getSafePath(userInput) {
const normalized = path.normalize(userInput).replace(/^(\/|\.\.)/, '');
const target = path.resolve(baseDir, normalized);
if (!target.startsWith(path.resolve(baseDir))) {
throw new Error('Invalid path: traversal attempt detected');
}
return target;
}
// Example usage in a Feathersjs hook
export default function () {
return async context => {
const { filename } = context.data;
const safePath = getSafePath(filename);
// Proceed with file operations using safePath
return context;
};
}
Firestore metadata with safe file references
Store only validated, relative paths or identifiers in Firestore, and keep filesystem-specific logic isolated in service hooks. This ensures that even if metadata is compromised, the attacker cannot directly manipulate filesystem paths.
import { Application } from '@feathersjs/feathers';
import { FirestoreService } from 'feathers-firebase-admin';
import { getFirestore } from 'firebase-admin/firestore';
const app: Application = {} as Application;
app.configure(new FirestoreService({
name: 'documents',
paginate: { default: 10, max: 50 },
getFirestore: () => getFirestore()
}));
// In a custom hook, map a safe filename to a Firestore document
app.service('documents').hooks({
before: {
async create(context) {
const safeName = context.data.name.replace(/[^a-zA-Z0-9._-]/g, '_');
// Store only safe names; avoid embedding directory paths in Firestore
context.data.name = safeName;
return context;
}
}
});
Validation libraries and allowlists
Use a validation library to enforce strict filename patterns and reject any input containing path traversal sequences. An allowlist approach is more robust than a blocklist.
import { validate } from 'validate-naming';
// Assume validate allows only alphanumeric, dash, underscore, and dot without slashes
if (!validate(filename)) {
throw new Error('Invalid filename format');
}
Least-privilege execution
Run the Feathersjs service with minimal filesystem permissions so that even if traversal occurs, the impact is limited. Avoid running as root and use dedicated service accounts with access only to required directories.