Archive extraction library supporting ZIP, TAR, TAR.GZ, TAR.BZ2, and BZIP2 formats with Promise-based API and security protections
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Decompress is an archive extraction library for Node.js that simplifies extracting files from various archive formats including ZIP, TAR, TAR.GZ, TAR.BZ2, and BZIP2. It provides a Promise-based API with advanced features like file filtering, path mapping, directory stripping, and built-in security protections against path traversal attacks.
npm install decompressconst decompress = require('decompress');For ES modules:
import decompress from 'decompress';const decompress = require('decompress');
// Extract archive to memory (returns file objects)
decompress('archive.zip').then(files => {
console.log(`Extracted ${files.length} files`);
files.forEach(file => {
console.log(file.path, file.type, file.data.length);
});
});
// Extract archive to directory
decompress('archive.zip', 'dist').then(files => {
console.log('Files extracted to dist directory');
});
// Extract with options
decompress('archive.tar.gz', 'output', {
strip: 1, // Remove first directory level
filter: file => file.path.endsWith('.js') // Only JS files
}).then(files => {
console.log('JavaScript files extracted');
});Main function for extracting archives with comprehensive format support and security protections.
/**
* Extract files from various archive formats
* @param {string|Buffer} input - Path to archive file or Buffer containing archive data
* @param {string} [output] - Output directory path (optional, omit to extract to memory only)
* @param {DecompressOptions} [opts] - Configuration options
* @returns {Promise<FileObject[]>} Promise resolving to array of extracted file objects
* @throws {TypeError} When input is neither string nor Buffer
*/
function decompress(input, output, opts);Supported Archive Formats:
Processing Pipeline:
Files are processed in the following order:
Usage Examples:
// Extract from file path to memory
const files = await decompress('data.zip');
// Extract from Buffer to memory
const buffer = fs.readFileSync('data.zip');
const files = await decompress(buffer);
// Extract to specific directory
await decompress('data.tar.gz', './extracted');
// Extract with custom options
await decompress('data.zip', 'output', {
strip: 2,
filter: file => !file.path.includes('test'),
map: file => {
file.path = 'prefix-' + file.path;
return file;
}
});Filter files during extraction to selectively extract only desired files.
/**
* Filter function to selectively extract files
* @param {FileObject} file - File object being processed
* @returns {boolean} true to extract the file, false to skip
*/
opts.filter = function(file) { return boolean; };Usage Example:
// Extract only JavaScript files
await decompress('project.zip', 'src', {
filter: file => file.path.endsWith('.js') || file.path.endsWith('.ts')
});
// Skip hidden files and directories
await decompress('backup.tar.gz', 'restore', {
filter: file => !file.path.startsWith('.')
});Transform file objects during extraction to modify paths, metadata, or content.
/**
* Map function to transform files during extraction
* @param {FileObject} file - File object being processed
* @returns {FileObject} Modified file object
*/
opts.map = function(file) { return FileObject; };Usage Example:
// Add prefix to all extracted file paths
await decompress('archive.zip', 'output', {
map: file => {
file.path = 'extracted-' + file.path;
return file;
}
});
// Modify file metadata
await decompress('data.tar', 'output', {
map: file => {
if (file.type === 'file') {
file.mode = file.mode | 0o644; // Set read permissions
}
return file;
}
});Remove leading directory components from extracted file paths.
/**
* Number of leading directory components to strip from file paths
* @type {number}
* @default 0
*/
opts.strip = number;Usage Example:
// Remove first directory level (e.g., "folder/file.txt" becomes "file.txt")
await decompress('nested-archive.tar.gz', 'flat-output', {
strip: 1
});
// Remove two directory levels
await decompress('deeply/nested/archive.zip', 'output', {
strip: 2
});Override default decompression plugins with custom handlers. Each plugin is a function that receives input buffer and options, returning a Promise of file objects.
/**
* Array of decompression plugins to use
* @type {Function[]}
* @default [decompressTar(), decompressTarbz2(), decompressTargz(), decompressUnzip()]
*/
opts.plugins = Array<Function>;
/**
* Plugin function interface
* @param {Buffer} input - Archive data as Buffer
* @param {DecompressOptions} opts - Configuration options
* @returns {Promise<FileObject[]>} Promise resolving to array of file objects
*/
function plugin(input, opts);Plugin Behavior:
Usage Example:
const decompressUnzip = require('decompress-unzip');
// Use only ZIP decompression
await decompress('data.zip', 'output', {
plugins: [decompressUnzip()]
});
// No plugins (useful for getting empty array when no plugins match)
const result = await decompress('unknown.xyz', {
plugins: []
});
// result will be []/**
* Configuration options for decompress function
*/
interface DecompressOptions {
/** Filter function to selectively extract files */
filter?: (file: FileObject) => boolean;
/** Map function to transform files during extraction */
map?: (file: FileObject) => FileObject;
/** Array of decompression plugins */
plugins?: Function[];
/** Number of leading directory components to strip */
strip?: number;
}
/**
* File object representing an extracted file
*/
interface FileObject {
/** File contents as Buffer */
data: Buffer;
/** File permissions/mode as number (affected by process umask when written) */
mode: number;
/** File modification time as Date object */
mtime: Date;
/** Relative file path within the archive */
path: string;
/** File type: 'file', 'directory', 'link', or 'symlink' */
type: 'file' | 'directory' | 'link' | 'symlink';
/** Target path for links and symlinks (only present when type is 'link' or 'symlink') */
linkname?: string;
}Different file types are handled as follows when extracting to disk:
fs.writeFile() with mode applied (affected by umask)makeDir() with timestamps set via fs.utimes()fs.link() to link to existing filesfs.symlink() (or fs.link() on Windows for compatibility)Platform Differences:
The decompress function throws errors with specific messages:
TypeError: "Input file required" when input is neither string nor BufferENOENT error when specified archive file doesn't exist"Refusing to create a directory outside the output path." for directory escapes"Refusing to write into a symlink" when attempting to write through symlinks"Refusing to write outside output directory: <path>" for file path escapesError Handling Example:
try {
const files = await decompress('archive.zip', 'output');
console.log('Extraction successful');
} catch (error) {
if (error instanceof TypeError && error.message === 'Input file required') {
console.error('Invalid input provided');
} else if (error.message.includes('Refusing')) {
console.error('Security violation detected:', error.message);
} else if (error.code === 'ENOENT') {
console.error('Archive file not found');
} else {
console.error('Extraction failed:', error.message);
}
}Decompress includes built-in security protections against malicious archives:
fs.realpath() to validate all paths are within allowed boundaries before file operationsSecurity Implementation Details:
These protections help prevent malicious archives from compromising the system through directory traversal attacks, symlink-based escapes, and other archive-based security vulnerabilities.