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 };
    }
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It effectively describes key traits: the MCP server reads locally and transfers, file size limits (200MB), and the option to bypass upload. However, it doesn't mention error handling, permissions needed, or what happens on success/failure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured and front-loaded with the core purpose. Each sentence adds value: first states the action, second explains the mechanism, third sets constraints, and fourth provides alternatives. No wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with no annotations and no output schema, the description does a good job covering purpose, constraints, and alternatives. However, it lacks details on return values or error conditions, which would be helpful given the mutation nature of uploading files.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema fully documents all parameters. The description adds no additional parameter-specific information beyond what's in the schema, such as format examples or edge cases. Baseline 3 is appropriate when schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the specific action ('Upload a file'), resource ('from the host filesystem to the samples directory'), and purpose ('for analysis'). It distinguishes from siblings like 'download_file' and 'download_from_url' by specifying the direction and source of transfer.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit guidance on when to use this tool (uploading for analysis) versus alternatives (bypassing upload by referencing absolute paths in analysis tools like 'get_file_info', 'analyze_file', or 'run_tool'). It also mentions the maximum file size constraint (200MB).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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