Skip to main content
Glama
REMnux

REMnux MCP Server

Official
by REMnux

run_tool

Execute malware analysis commands on REMnux to extract strings, analyze files, and investigate suspicious samples using tools like pestr and strings.

Instructions

Execute a command in REMnux. Supports piped commands (e.g., 'oledump.py sample.doc | grep VBA'). String extraction: For PE files use 'pestr'; for non-PE use 'strings' (ASCII) and 'strings -el' (Unicode).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
commandYesCommand to execute (can include pipes, e.g., 'strings sample.exe | grep -i password')
input_fileNoInput file path (relative to samples dir, or absolute path in local mode) - appended to command
timeoutNoTimeout in seconds (default: 300)

Implementation Reference

  • Main handler function handleRunTool that executes commands in REMnux. Implements path validation, command security checks, discouraged pattern warnings, command execution, output truncation, auto-parsing, and advisory generation.
    export async function handleRunTool(
      deps: HandlerDeps,
      args: RunToolArgs
    ) {
      const startTime = Date.now();
      const { connector, config } = deps;
    
      // Build full command
      let fullCommand = args.command;
      if (args.input_file) {
        // Validate input file path (skip unless --sandbox)
        if (!config.noSandbox) {
          const pathValidation = validateFilePath(args.input_file, config.samplesDir);
          if (!pathValidation.safe) {
            return formatError("run_tool", new REMnuxError(
              pathValidation.error || "Invalid input file path",
              "INVALID_PATH",
              "validation",
              "Use a relative path within the samples directory",
            ), startTime);
          }
        }
        // Append quoted file path to command (single-quotes prevent shell expansion)
        // Escape any single quotes in the path as defense-in-depth (isPathSafe also rejects them)
        const escapedFile = args.input_file.replace(/'/g, "'\\''");
        const resolvedInputFile = (config.mode === "local" && args.input_file.startsWith("/")) ? escapedFile : `${config.samplesDir}/${escapedFile}`;
        fullCommand = `${args.command} '${resolvedInputFile}'`;
      }
    
      // Security: Validate command against blocklist
      const validation = isCommandSafe(fullCommand);
      if (!validation.safe) {
        return formatError("run_tool", new REMnuxError(
          validation.error || "Command blocked",
          "COMMAND_BLOCKED",
          "security",
          "This command is blocked for security reasons. Use an allowed tool instead.",
        ), startTime);
      }
    
      // Check for discouraged patterns BEFORE execution (unless acknowledged)
      // Use regex to ensure --acknowledge-raw is a flag, not part of a filename
      const hasAcknowledge = /(?:^|\s)--acknowledge-raw(?:\s|$)/.test(args.command);
      if (!hasAcknowledge) {
        const discouraged = checkDiscouragedPattern(fullCommand);
        if (discouraged) {
          return formatResponse(
            "run_tool",
            {
              warning: discouraged.warning,
              suggestion: discouraged.suggestion,
              command_blocked: true,
              note: "Command was NOT executed. Use suggested alternatives or add --acknowledge-raw to proceed.",
            },
            startTime
          );
        }
      }
    
      const MAX_STDOUT_RESPONSE = 100 * 1024; // 100KB — fits in LLM context
      const MAX_STDERR_RESPONSE = 50 * 1024;
    
      try {
        const execOptions: { timeout: number; cwd?: string } = {
          timeout: (args.timeout || config.timeout) * 1000,
        };
    
        // Only set cwd when input_file is provided (file-based analysis)
        if (args.input_file) {
          execOptions.cwd = config.samplesDir;
        }
    
        const result = await connector.executeShell(fullCommand, execOptions);
    
        let stdout = result.stdout || "";
        let stderr = result.stderr || "";
    
        stderr = filterStderrNoise(stderr);
    
        let truncated = false;
        const fullStdoutLength = stdout.length;
    
        if (stdout.length > MAX_STDOUT_RESPONSE) {
          stdout = stdout.slice(0, MAX_STDOUT_RESPONSE);
          truncated = true;
        }
        if (stderr.length > MAX_STDERR_RESPONSE) {
          stderr = stderr.slice(0, MAX_STDERR_RESPONSE);
          truncated = true;
        }
    
        // Auto-parse output if command matches a known tool with a parser
        let findings;
        let parsedMetadata;
        const toolName = detectToolName(fullCommand);
        if (toolName && hasParser(toolName) && stdout) {
          const parsed = parseToolOutput(toolName, stdout);
          if (parsed.parsed) {
            findings = parsed.findings;
            parsedMetadata = parsed.metadata;
          }
        }
    
        // Check for advisory (non-blocking guidance)
        const advisory = getCommandAdvisory(fullCommand);
    
        return formatResponse("run_tool", {
          command: fullCommand,
          stdout,
          stderr,
          exit_code: result.exitCode,
          truncated,
          ...(truncated && {
            truncation_notice: "Output exceeded response size limit. Use 'run_tool' with '| head -N' or redirect to a file for full output.",
            full_stdout_length: fullStdoutLength,
          }),
          ...(findings && { findings, parsed_metadata: parsedMetadata }),
          ...(advisory && { advisory }),
        }, startTime);
      } catch (error) {
        return formatError("run_tool", toREMnuxError(error, config.mode), startTime);
      }
    }
  • Zod schema definition for run_tool input validation. Defines three parameters: command (required string), input_file (optional string), and timeout (optional number with default 300).
    export const runToolSchema = z.object({
      command: z.string().describe("Command to execute (can include pipes, e.g., 'strings sample.exe | grep -i password')"),
      input_file: z.string().optional().describe("Input file path (relative to samples dir, or absolute path in local mode) - appended to command"),
      timeout: z.number().optional().describe("Timeout in seconds (default: 300)"),
    });
    export type RunToolArgs = z.infer<typeof runToolSchema>;
  • src/index.ts:94-101 (registration)
    MCP tool registration for run_tool. Registers the tool with name 'run_tool', description, schema validation (runToolSchema.shape), and maps to handleRunTool handler function.
    // Tool: run_tool - Execute a command in REMnux
    server.tool(
      "run_tool",
      "Execute a command in REMnux. Supports piped commands (e.g., 'oledump.py sample.doc | grep VBA'). " +
      "String extraction: For PE files use 'pestr'; for non-PE use 'strings' (ASCII) and 'strings -el' (Unicode).",
      runToolSchema.shape,
      (args) => handleRunTool(deps, args)
    );
  • Security validation function isCommandSafe that checks commands against blocked patterns (null byte injection, catastrophic commands). Called by handleRunTool to prevent dangerous command execution.
    export function isCommandSafe(command: string): { safe: boolean; error?: string } {
      // Reject empty or whitespace-only commands
      if (!command || command.trim() === "") {
        return { safe: false, error: "Empty command" };
      }
    
      // Check against blocked patterns
      for (const { pattern, category } of BLOCKED_PATTERNS) {
        if (pattern.test(command)) {
          return { safe: false, error: `Command blocked: ${category}` };
        }
      }
    
      // Check against dangerous pipe patterns
      for (const { pattern, category } of DANGEROUS_PIPE_PATTERNS) {
        if (pattern.test(command)) {
          return { safe: false, error: `Command blocked: ${category}` };
        }
      }
    
      return { safe: true };
    }
  • Utility function filterStderrNoise that removes common non-actionable warnings from tool stderr output (Volatility progress bars, Python warnings, compatibility notices). Called by handleRunTool to clean stderr before response.
    export function filterStderrNoise(stderr: string): string {
      return stderr
        // Volatility 3 progress bars
        .replace(/^Progress:\s+[\d.]+\s+.*$/gm, "")
        // Python SyntaxWarning / DeprecationWarning lines
        .replace(/^.*(?:SyntaxWarning|DeprecationWarning):.*$/gm, "")
        // Python version compatibility notices
        .replace(/^.*(?:This version of|requires Python).*$/gm, "")
        // Python source context lines (indented code following warnings)
        .replace(/^\s+\^+\s*$/gm, "")
        .trim();
    }

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