Module: LanguageOperator::Dsl::Shell

Defined in:
lib/language_operator/dsl/shell.rb

Overview

Safe shell command execution for MCP tools

Provides methods for executing shell commands safely with automatic argument escaping to prevent injection attacks. All methods are class methods.

Examples:

Basic usage

result = Shell.run('ls', '-la', '/tmp')
if result[:success]
  puts result[:output]
end

Safe user input

# User input is automatically escaped
result = Shell.run('grep', user_input, '/etc/hosts')

Class Method Summary collapse

Class Method Details

.build(cmd, *args) ⇒ Object

Safely build a command string with escaped arguments Useful when you need to construct a command but not execute it yet



114
115
116
117
# File 'lib/language_operator/dsl/shell.rb', line 114

def self.build(cmd, *args)
  escaped_args = args.map { |arg| Shellwords.escape(arg.to_s) }
  "#{cmd} #{escaped_args.join(' ')}"
end

.capture(cmd) ⇒ Object

Run a command and return only stdout (like backticks) Returns nil if the command fails



77
78
79
80
# File 'lib/language_operator/dsl/shell.rb', line 77

def self.capture(cmd, *, **)
  result = run(cmd, *, **)
  result[:success] ? result[:output] : nil
end

.capture!(cmd) ⇒ Object

Run a command and return stdout, raising on failure



83
84
85
86
87
88
# File 'lib/language_operator/dsl/shell.rb', line 83

def self.capture!(cmd, *, **)
  result = run(cmd, *, **)
  raise "Command failed (exit #{result[:exitcode]}): #{result[:error]}" unless result[:success]

  result[:output]
end

.command_exists?(cmd) ⇒ Boolean

Check if a command exists in PATH

Returns:

  • (Boolean)


91
92
93
94
# File 'lib/language_operator/dsl/shell.rb', line 91

def self.command_exists?(cmd)
  result = run('which', cmd)
  result[:success]
end

.escape(arg) ⇒ Object

Escape a single argument for shell usage



120
121
122
# File 'lib/language_operator/dsl/shell.rb', line 120

def self.escape(arg)
  Shellwords.escape(arg.to_s)
end

.raw(_command, env: {}, chdir: nil, timeout: 30) ⇒ Object

Deprecated.

This method has been removed for security reasons

Execute raw shell command (REMOVED FOR SECURITY) This method has been removed as it allowed arbitrary shell execution with pipes, redirects, etc. which is a security risk in synthesized code.

Raises:



108
109
110
# File 'lib/language_operator/dsl/shell.rb', line 108

def self.raw(_command, env: {}, chdir: nil, timeout: 30)
  raise SecurityError, 'Shell.raw has been removed for security reasons. Use Shell.run with explicit arguments instead.'
end

.run(cmd, *args, env: {}, chdir: nil, timeout: 30) ⇒ Hash

Run a shell command with properly escaped arguments

This is safer than using backticks as it prevents shell injection. Arguments are automatically escaped using Shellwords.

Parameters:

  • cmd (String)

    Command to execute

  • args (Array<String>)

    Arguments (will be escaped)

  • env (Hash) (defaults to: {})

    Environment variables to set

  • chdir (String, nil) (defaults to: nil)

    Working directory

  • timeout (Integer) (defaults to: 30)

    Timeout in seconds (default: 30)

Returns:

  • (Hash)

    Result with :success, :output, :error, :exitcode, :timeout keys



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/language_operator/dsl/shell.rb', line 35

def self.run(cmd, *args, env: {}, chdir: nil, timeout: 30)
  # Escape all arguments
  escaped_args = args.map { |arg| Shellwords.escape(arg.to_s) }
  full_cmd = "#{cmd} #{escaped_args.join(' ')}"

  # Execute with timeout
  stdout = nil
  stderr = nil
  status = nil

  begin
    Timeout.timeout(timeout) do
      stdout, stderr, status = Open3.capture3(env, full_cmd, chdir: chdir)
    end
  rescue Timeout::Error
    return {
      success: false,
      output: '',
      error: "Command timed out after #{timeout} seconds",
      exitcode: -1,
      timeout: true
    }
  rescue StandardError => e
    return {
      success: false,
      output: '',
      error: e.message,
      exitcode: -1
    }
  end

  {
    success: status.success?,
    output: stdout,
    error: stderr,
    exitcode: status.exitstatus,
    timeout: false
  }
end

.spawn(_cmd, *_args, env: {}, chdir: nil) ⇒ Object

Deprecated.

This method has been removed for security reasons

Run a command in the background and return immediately

Raises:



99
100
101
# File 'lib/language_operator/dsl/shell.rb', line 99

def self.spawn(_cmd, *_args, env: {}, chdir: nil)
  raise SecurityError, 'Shell.spawn has been removed for security reasons. Background process execution is not allowed in synthesized code.'
end