Skip to main content
Glama
REMnux

REMnux MCP Server

Official
by REMnux

upload_from_host

Upload files from your host system to the REMnux malware analysis environment for examination. Transfer files up to 200MB to the samples directory for use with analysis tools.

Instructions

Upload a file from the host filesystem to the samples directory for analysis. Accepts an absolute host path — the MCP server reads the file locally and transfers it. Maximum file size: 200MB. Files can also be referenced by absolute path in analysis tools, bypassing the need to upload. For files outside the samples directory, pass the full path to get_file_info, analyze_file, or run_tool.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
host_pathYesAbsolute path on the host filesystem to the file to upload
filenameNoOverride filename in samples dir (defaults to basename of host_path)
overwriteNoWhether to overwrite if file exists. Default: false

Implementation Reference

  • Main handler function that validates the host path and filename, then calls uploadSampleFromHost to transfer the file from the host filesystem to the samples directory. Includes error handling and response formatting.
    export async function handleUploadFromHost( deps: HandlerDeps, args: UploadFromHostArgs ) { const startTime = Date.now(); const { connector, config } = deps; // Validate host path first const pathValidation = validateHostPath(args.host_path); if (!pathValidation.valid) { return formatError("upload_from_host", new REMnuxError( pathValidation.error || "Invalid host path", "INVALID_PATH", "validation", "Provide an absolute path to a file on the host filesystem", ), startTime); } // Validate filename if provided const targetFilename = args.filename ?? basename(args.host_path); const filenameValidation = validateFilename(targetFilename); if (!filenameValidation.valid) { return formatError("upload_from_host", new REMnuxError( filenameValidation.error || "Invalid filename", "INVALID_FILENAME", "validation", "Use alphanumeric characters, hyphens, underscores, and dots only", ), startTime); } try { const result = await uploadSampleFromHost( connector, config.samplesDir, args.host_path, args.filename, args.overwrite, config.mode, ); if (result.success) { return formatResponse("upload_from_host", result as unknown as Record<string, unknown>, startTime); } else { return formatError("upload_from_host", new REMnuxError( result.error || "Upload failed", "UPLOAD_FAILED", "tool_failure", "Check that the file exists and is readable on the host filesystem", ), startTime); } } catch (error) { return formatError("upload_from_host", toREMnuxError(error, config.mode), startTime); } }
  • src/index.ts:127-156 (registration)
    Tool registration in the MCP server. Includes mode-specific description text that adapts based on whether running in local, SSH, or Docker mode. Registers the handler with the schema validation.
    // Tool: upload_from_host - Upload a file from the host filesystem const uploadDescription = (() => { const base = "Upload a file from the host filesystem to the samples directory for analysis. " + "Accepts an absolute host path — the MCP server reads the file locally and transfers it. " + "Maximum file size: 200MB. "; switch (config.mode) { case "local": return base + "Files can also be referenced by absolute path in analysis tools, bypassing the need to upload. " + "For files outside the samples directory, pass the full path to get_file_info, analyze_file, or run_tool."; case "ssh": return base + "For larger files (memory images, disk images, PCAPs), " + "place them directly in the samples directory on the remote host via scp/sftp, " + "then use list_files to confirm."; default: return base + "For larger files (memory images, disk images, PCAPs), " + "use a Docker bind mount instead: " + "docker run -v /host/evidence:/home/remnux/files/samples/evidence remnux/remnux-distro. " + "For HTTP transport deployments, use scp/sftp to place files in the samples directory directly, " + "then use list_files to confirm."; } })(); server.tool( "upload_from_host", uploadDescription, uploadFromHostSchema.shape, (args) => handleUploadFromHost(deps, args) );
  • Zod schema defining the input parameters for upload_from_host tool: host_path (required absolute path), filename (optional override), and overwrite (optional boolean flag).
    export const uploadFromHostSchema = z.object({ host_path: z.string().describe("Absolute path on the host filesystem to the file to upload"), filename: z.string().optional().describe("Override filename in samples dir (defaults to basename of host_path)"), overwrite: z.boolean().optional().default(false).describe("Whether to overwrite if file exists. Default: false"), }); export type UploadFromHostArgs = z.infer<typeof uploadFromHostSchema>;
  • Core upload implementation that validates the host path, checks file size (200MB limit), calculates SHA256 hash via streaming, and writes the file using the connector's writeFileFromPath method. Includes symlink rejection and existence checks.
    export async function uploadSampleFromHost( connector: Connector, samplesDir: string, hostPath: string, filename?: string, overwrite: boolean = false, mode: "docker" | "ssh" | "local" = "docker", ): Promise<UploadResult> { // Validate host path const pathValidation = validateHostPath(hostPath); if (!pathValidation.valid) { return { success: false, error: pathValidation.error }; } // Reject symlinks let stat; try { stat = lstatSync(hostPath); } catch (_err) { return { success: false, error: `File not found: ${hostPath}` }; } if (stat.isSymbolicLink()) { return { success: false, error: "Symlinks are not allowed" }; } if (!stat.isFile()) { return { success: false, error: "host_path must point to a regular file" }; } // Check file size via stat (no buffering) if (stat.size > MAX_FILE_SIZE) { const sizeMB = (stat.size / 1024 / 1024).toFixed(0); const limitMB = MAX_FILE_SIZE / 1024 / 1024; const advice = mode === "local" ? `Use absolute paths in analysis tools to reference the file directly without uploading.` : mode === "ssh" ? `Place large files directly in the samples directory on the remote host via scp/sftp.` : `For large files such as memory images, mount a host directory into the container instead:\n` + ` docker run -v /path/to/evidence:/home/remnux/files/samples/evidence remnux/remnux-distro\n` + `Then reference files as: evidence/<filename>`; return { success: false, error: `File size (${sizeMB}MB) exceeds the ${limitMB}MB upload limit. ${advice}`, }; } // Determine target filename const targetFilename = filename ?? basename(hostPath); // Validate target filename const filenameValidation = validateFilename(targetFilename); if (!filenameValidation.valid) { return { success: false, error: filenameValidation.error }; } // Calculate SHA256 hash via streaming let sha256: string; try { const hash = createHash("sha256"); await pipeline(createReadStream(hostPath), hash); sha256 = hash.digest("hex"); } catch (err) { return { success: false, error: `Failed to read file: ${err instanceof Error ? err.message : "Unknown error"}`, }; } // Build full file path const filePath = `${samplesDir}/${targetFilename}`; // Check if file already exists (unless overwrite is true) if (!overwrite) { try { const checkResult = await connector.execute(["test", "-e", filePath], { timeout: 5000, }); if (checkResult.exitCode === 0) { return { success: false, error: "File already exists. Use overwrite=true to replace.", }; } } catch { // test command failed, file probably doesn't exist } } // Ensure target directory exists (fresh containers may lack it) try { await connector.execute(["mkdir", "-p", samplesDir], { timeout: 5000 }); } catch { // Ignore — real errors surface in writeFileFromPath } // Write file using connector's streaming path-based method try { await connector.writeFileFromPath(filePath, hostPath); } catch (err) { return { success: false, error: `Failed to write file: ${err instanceof Error ? err.message : "Unknown error"}`, }; } return { success: true, path: filePath, size_bytes: stat.size, sha256, }; }
  • Security validation functions for filenames and host paths. Rejects path traversal (..), shell metacharacters, null bytes, and ensures absolute paths for host_path.
    export function validateFilename(filename: string): { valid: boolean; error?: string } { if (!filename || filename.length === 0) { return { valid: false, error: "Filename cannot be empty" }; } if (filename.length > 255) { return { valid: false, error: "Filename too long (max 255 characters)" }; } if (filename.includes("/") || filename.includes("\\")) { return { valid: false, error: "Filename cannot contain path separators" }; } if (filename.includes("..")) { return { valid: false, error: "Filename cannot contain '..'" }; } if (filename.includes("\0")) { return { valid: false, error: "Filename cannot contain null bytes" }; } // Check for shell metacharacters that could cause issues if (/[;&|`$\n\r'"<>]/.test(filename)) { return { valid: false, error: "Filename contains invalid characters" }; } return { valid: true }; } /** * Validate a host filesystem path for security */ export function validateHostPath(hostPath: string): { valid: boolean; error?: string } { if (!hostPath || hostPath.length === 0) { return { valid: false, error: "host_path cannot be empty" }; } if (!isAbsolute(hostPath)) { return { valid: false, error: "host_path must be an absolute path" }; } if (hostPath.includes("\0")) { return { valid: false, error: "host_path cannot contain null bytes" }; } // Reject path traversal — always reject ".." components if (hostPath.includes("..")) { return { valid: false, error: "host_path cannot contain path traversal (..)" }; } // Reject shell metacharacters if (/[;&|`$\n\r'"<>]/.test(hostPath)) { return { valid: false, error: "host_path contains invalid characters" }; } return { valid: true }; }

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