Skip to main content
Glama
REMnux

REMnux MCP Server

Official
by REMnux

extract_archive

Extract files from compressed archives (.zip, .7z, .rar) for malware analysis, automatically trying common passwords if protected.

Instructions

Extract files from a compressed archive (.zip, .7z, .rar). Automatically tries common malware passwords if the archive is password-protected. Returns list of extracted files.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
archive_fileYesPath to archive file relative to samples directory (e.g., 'sample.zip')
passwordNoOptional password to try first. If not provided, tries common passwords from built-in list.
output_subdirNoOptional subdirectory name for extracted files. Defaults to archive filename without extension.

Implementation Reference

  • Main handler function handleExtractArchive that validates inputs, checks archive type, validates paths for security, calls extractArchive, and formats the response with extracted files list and password used
    export async function handleExtractArchive( deps: HandlerDeps, args: ExtractArchiveArgs ) { const startTime = Date.now(); const { connector, config } = deps; // Validate archive file path (skip unless --sandbox) if (!config.noSandbox) { const validation = validateFilePath(args.archive_file, config.samplesDir); if (!validation.safe) { return formatError("extract_archive", new REMnuxError( validation.error || "Invalid archive file path", "INVALID_PATH", "validation", "Use a relative path within the samples directory", ), startTime); } } // Security: Validate output subdirectory if provided if (args.output_subdir && !config.noSandbox) { if (args.output_subdir.trim() === "") { return formatError("extract_archive", new REMnuxError( "output_subdir cannot be blank", "INVALID_SUBDIR", "validation", "Provide a non-empty subdirectory name without special characters", ), startTime); } if (args.output_subdir.includes("..") || args.output_subdir.includes("/") || args.output_subdir.includes("\\")) { return formatError("extract_archive", new REMnuxError( "Invalid output subdirectory name", "INVALID_SUBDIR", "validation", "Subdirectory must not contain path separators or '..'", ), startTime); } if (/[;&|`$\n\r'"\\]/.test(args.output_subdir)) { return formatError("extract_archive", new REMnuxError( "Output subdirectory contains invalid characters", "INVALID_SUBDIR", "validation", "Use only alphanumeric characters, hyphens, and underscores", ), startTime); } } // Verify archive type is supported const archiveType = detectArchiveType(args.archive_file); if (!archiveType) { return formatError("extract_archive", new REMnuxError( "Unsupported archive format. Supported: .zip, .7z, .rar", "UNSUPPORTED_FORMAT", "validation", "Rename the file with a supported extension or use a different tool", ), startTime); } // Build full archive path const archivePath = `${config.samplesDir}/${args.archive_file}`; try { const result = await extractArchive( connector, archivePath, config.samplesDir, args.password, args.output_subdir ); if (result.success) { // Store archive metadata for download_file to reuse if (result.password && archiveType) { deps.sessionState.storeArchiveInfo( args.archive_file, result.files, archiveType, result.password ); } return formatResponse("extract_archive", { extracted_to: result.outputDir, files: result.files, file_count: result.files.length, password_used: result.password || "(none - archive was not encrypted)", }, startTime); } else { return formatError("extract_archive", new REMnuxError( result.error || "Extraction failed", "EXTRACTION_FAILED", "tool_failure", "Check that the archive is valid and not corrupted", ), startTime); } } catch (error) { return formatError("extract_archive", toREMnuxError(error, deps.config.mode), startTime); } }
  • Input schema definition for extract_archive tool using Zod validation, defining archive_file, password (optional), and output_subdir (optional) parameters
    export const extractArchiveSchema = z.object({ archive_file: z.string().describe("Path to archive file relative to samples directory (e.g., 'sample.zip')"), password: z.string().optional().describe("Optional password to try first. If not provided, tries common passwords from built-in list."), output_subdir: z.string().optional().describe("Optional subdirectory name for extracted files. Defaults to archive filename without extension."), }); export type ExtractArchiveArgs = z.infer<typeof extractArchiveSchema>;
  • src/index.ts:119-125 (registration)
    Tool registration in MCP server, registering extract_archive with its description, schema, and handler function
    // Tool: extract_archive - Extract files from compressed archives server.tool( "extract_archive", "Extract files from a compressed archive (.zip, .7z, .rar). Automatically tries common malware passwords if the archive is password-protected. Returns list of extracted files.", extractArchiveSchema.shape, (args) => handleExtractArchive(deps, args) );
  • Core extraction logic in extractArchive function that supports .zip, .7z, and .rar formats with automatic password detection, zip-slip protection, and pre-extraction validation of archive entries
    export async function extractArchive( connector: Connector, archivePath: string, samplesDir: string, customPassword?: string, outputSubdir?: string ): Promise<ExtractionResult> { // Detect archive type const archiveType = detectArchiveType(archivePath); if (!archiveType) { return { success: false, files: [], error: `Unsupported archive format: ${extname(archivePath)}`, outputDir: "", }; } // Determine output directory const archiveName = basename(archivePath, extname(archivePath)); const subdir = outputSubdir || archiveName; const outputDir = join(samplesDir, subdir); // Create output directory const mkdirResult = await connector.execute(["mkdir", "-p", outputDir], { timeout: 10000, }); if (mkdirResult.exitCode !== 0) { return { success: false, files: [], error: `Failed to create output directory: ${mkdirResult.stderr}`, outputDir, }; } // Pre-extraction zip-slip check: inspect archive entries before extracting const entries = await listArchiveEntries(connector, archivePath, archiveType); if (entries.length > 0) { const traversalEntries = checkEntriesForTraversal(entries); if (traversalEntries.length > 0) { return { success: false, files: [], error: `Archive contains path traversal entries (zip-slip): ${traversalEntries.slice(0, 3).join(", ")}${traversalEntries.length > 3 ? ` and ${traversalEntries.length - 3} more` : ""}`, outputDir, }; } } // Build password list to try const passwordsToTry: (string | undefined)[] = []; // 1. Custom password first if provided if (customPassword) { passwordsToTry.push(customPassword); } // 2. Try without password (archive might not be encrypted) passwordsToTry.push(undefined); // 3. Add passwords from config file const configPasswords = loadPasswordList(); for (const pwd of configPasswords) { if (pwd !== customPassword) { // Avoid duplicates passwordsToTry.push(pwd); } } // Try extraction with each password let lastError = ""; for (const password of passwordsToTry) { try { const cmd = getExtractionCommand(archiveType, archivePath, outputDir, password); const result = await connector.execute(cmd, { timeout: 120000 }); if (result.exitCode === 0) { // Success! List extracted files const files = await listExtractedFiles(connector, outputDir); // Validate for zip-slip attacks (path traversal in archive entries) const escapeAttempts = validateExtractedPaths(files, outputDir); if (escapeAttempts.length > 0) { // Clean up potentially malicious files await connector.execute(["rm", "-rf", outputDir], { timeout: 30000 }); return { success: false, files: [], error: `Archive contains path escape attempts (zip-slip): ${escapeAttempts.slice(0, 3).join(", ")}${escapeAttempts.length > 3 ? ` and ${escapeAttempts.length - 3} more` : ""}`, outputDir, }; } return { success: true, files, password: password, // undefined if no password was needed outputDir, }; } // Check if this is a password error if (isWrongPasswordError(result, archiveType)) { lastError = "Incorrect password"; continue; // Try next password } // Some other error - might still try other passwords for encrypted archives lastError = result.stderr || result.stdout || "Unknown extraction error"; } catch (err) { lastError = err instanceof Error ? err.message : "Extraction failed"; } } // All passwords failed return { success: false, files: [], error: `Extraction failed: ${lastError}. Tried ${passwordsToTry.length} password(s).`, outputDir, }; }
  • Helper function detectArchiveType that identifies archive format from file extension (.zip, .7z, .rar)
    export function detectArchiveType(filename: string): ArchiveType { const ext = extname(filename).toLowerCase(); switch (ext) { case ".zip": return "zip"; case ".7z": return "7z"; case ".rar": return "rar"; default: return null; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/REMnux/remnux-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server