Module: OllamaAgent::PathSandbox

Defined in:
lib/ollama_agent/path_sandbox.rb

Overview

Resolves paths under a project root using File.realpath so symlinks cannot escape the sandbox.

Class Method Summary collapse

Class Method Details

.allowed?(root_abs, root_real, user_path) ⇒ Boolean

Parameters:

  • root_abs (String)

    File.expand_path of the project root (may be a symlink path)

  • root_real (String)

    File.realpath(root_abs) when the root exists

  • user_path (String)

    relative or absolute path from tool args

Returns:

  • (Boolean)


13
14
15
16
17
18
19
20
# File 'lib/ollama_agent/path_sandbox.rb', line 13

def allowed?(root_abs, root_real, user_path)
  return false if user_path.nil? || user_path.to_s.strip.empty?

  expanded = Pathname(user_path.to_s).expand_path(root_abs).cleanpath.to_s
  return false unless lexically_under_root_abs?(expanded, root_abs)

  candidate_under_root?(expanded, root_real, root_abs)
end

.candidate_under_root?(expanded_abs, root_real, root_abs) ⇒ Boolean

Returns:

  • (Boolean)


26
27
28
29
30
31
32
33
# File 'lib/ollama_agent/path_sandbox.rb', line 26

def candidate_under_root?(expanded_abs, root_real, root_abs)
  path_real = File.realpath(expanded_abs)
  under_root_real?(path_real, root_real)
rescue Errno::ENOENT
  nonexistent_path_allowed_under_root?(expanded_abs, root_real, root_abs)
rescue Errno::ELOOP, Errno::EACCES
  false
end

.lexically_under_root_abs?(expanded_abs, root_abs) ⇒ Boolean

Returns:

  • (Boolean)


22
23
24
# File 'lib/ollama_agent/path_sandbox.rb', line 22

def lexically_under_root_abs?(expanded_abs, root_abs)
  expanded_abs == root_abs || expanded_abs.start_with?(root_abs + File::SEPARATOR)
end

.nonexistent_path_allowed_under_root?(expanded_abs, root_real, root_abs) ⇒ Boolean

rubocop:disable Metrics/MethodLength – parent walk for missing path segments

Returns:

  • (Boolean)


40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/ollama_agent/path_sandbox.rb', line 40

def nonexistent_path_allowed_under_root?(expanded_abs, root_real, root_abs)
  parent = expanded_abs
  loop do
    next_parent = File.dirname(parent)
    break if next_parent == parent

    parent = next_parent
    begin
      pr = File.realpath(parent)
      return false unless under_root_real?(pr, root_real)

      return lexically_under_root_abs?(expanded_abs, root_abs)
    rescue Errno::ENOENT
      next
    rescue Errno::ELOOP, Errno::EACCES
      return false
    end
  end
  false
end

.under_root_real?(path_real, root_real) ⇒ Boolean

Returns:

  • (Boolean)


35
36
37
# File 'lib/ollama_agent/path_sandbox.rb', line 35

def under_root_real?(path_real, root_real)
  path_real == root_real || path_real.start_with?(root_real + File::SEPARATOR)
end