Class: SwarmSDK::Permissions::Validator

Inherits:
SimpleDelegator
  • Object
show all
Defined in:
lib/swarm_sdk/permissions/validator.rb

Overview

Validator decorates tools to enforce permission checks before execution

Uses the Decorator pattern (via SimpleDelegator) to wrap tool instances and validate file paths and commands before allowing tool execution.

Example:

write_tool = Tools::Write.new
permissions = Config.new(
{
  allowed_paths: ["tmp/**/*"],
  allowed_commands: ["^git (status|diff)$"]
},
base_directories: ["."]
)
validated_tool = Validator.new(write_tool, permissions)

# This will be denied:
validated_tool.call({"file_path" => "/etc/passwd", "content" => "..."})

Instance Method Summary collapse

Constructor Details

#initialize(tool, permissions_config) ⇒ Validator

Initialize validator decorator

Parameters:

  • tool (RubyLLM::Tool)

    Tool instance to wrap

  • permissions_config (Config)

    Permission configuration



28
29
30
31
32
# File 'lib/swarm_sdk/permissions/validator.rb', line 28

def initialize(tool, permissions_config)
  super(tool)
  @permissions = permissions_config
  @tool = tool
end

Instance Method Details

#call(args) ⇒ String

Intercept RubyLLM's call method to validate permissions

RubyLLM calls tool.call(args) where args have string keys. We must override call (not execute) because SimpleDelegator doesn't automatically intercept methods defined in the superclass.

Parameters:

  • args (Hash)

    Tool arguments with string keys

Returns:

  • (String)

    Tool result or permission denied message



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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/swarm_sdk/permissions/validator.rb', line 42

def call(args)
  # Validate Bash commands if this is the Bash tool
  if bash_tool?
    command = args["command"]
    if command && !@permissions.command_allowed?(command)
      # Find the specific pattern that blocks this command
      matching_pattern = @permissions.find_blocking_command_pattern(command)

      return ErrorFormatter.command_permission_denied(
        command: command,
        allowed_patterns: @permissions.allowed_commands,
        denied_patterns: @permissions.denied_commands,
        matching_pattern: matching_pattern,
        tool_name: @tool.name,
      )
    end
  end

  # Extract paths from arguments (handles both string and symbol keys)
  paths = extract_paths_from_args(args)

  # Determine if this is a directory search tool (Glob/Grep)
  directory_search = directory_search_tool?

  # Validate each path
  paths.each do |path|
    next if @permissions.allowed?(path, directory_search: directory_search)

    # Show absolute path in error message for clarity
    absolute_path = @permissions.to_absolute(path)

    # Find the specific pattern that blocks this path
    matching_pattern = @permissions.find_blocking_pattern(path, directory_search: directory_search)

    return ErrorFormatter.permission_denied(
      path: absolute_path,
      allowed_patterns: @permissions.allowed_patterns,
      denied_patterns: @permissions.denied_patterns,
      matching_pattern: matching_pattern,
      tool_name: @tool.name,
    )
  end

  # All permissions validated, call wrapped tool
  __getobj__.call(args)
end