Module: OllamaChat::Utils::PathValidator

Included in:
Tools::RunTests
Defined in:
lib/ollama_chat/utils/path_validator.rb

Overview

Utility module that centralises path‑validation logic for OllamaChat tools.

The module is deliberately kept private – it is mixed into tool classes that need to validate file paths against a whitelist. By extracting the logic into a reusable mixin we avoid duplication and make the intent of each tool explicit.

Instance Method Summary collapse

Instance Method Details

#assert_valid_path(path, allowed, check: nil) ⇒ Pathname

Validates that a supplied path is located inside one of the allowed directories.

The method performs the following steps:

  • Canonicalises the supplied ‘path` using `Pathname#expand_path` and `cleanpath(true)`.

  • If the ‘check` option is provided, resolves the path to its real physical location using `realpath` to prevent symlink traversal attacks.

  • Normalises each entry in ‘allowed` into an absolute, cleaned `Pathname` (an empty or `nil` allowed list results in an empty array, which causes every path to be rejected – the safest default).

  • Checks that the canonicalised ‘path` starts with any of the allowed directories.

  • If the check fails, raises an ‘OllamaChat::InvalidPathError` that carries the offending path for debugging and error reporting.

Parameters:

  • path (String, Pathname)

    The file or directory path to validate.

  • allowed (String, Array<String>, nil)

    A list of directory paths that are considered safe. Each entry is expanded to an absolute ‘Pathname`. Passing `nil` or an empty array will reject all paths.

  • check (Hash) (defaults to: nil)

    a customizable set of options

Options Hash (check:):

  • Validation (Symbol, Boolean, nil)

    mode:

    • ‘:file` - Verifies that the path exists and is a file.

    • ‘:directory` - Verifies that the path exists and is a directory.

    • ‘true` - Verifies only that the path exists (resolves symlinks).

    • ‘true` - Verifies only that the path does not exist (resolves symlinks).

    • ‘nil` (default) - No existence or type check is performed.

Returns:

  • (Pathname)

    The canonicalised absolute path if validation passes.

Raises:

  • (OllamaChat::InvalidPathError)

    when ‘path` is not inside any of the allowed directories or, when `check` is set, when the path does not exist or is of the wrong type.



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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/ollama_chat/utils/path_validator.rb', line 40

def assert_valid_path(path, allowed, check: nil)
  target_path = Pathname.new(path).expand_path.cleanpath(true)

  target_path.dirname.directory? or
    raise OllamaChat::InvalidPathError,
      "#{target_path.dirname.to_s.inspect} is not a directory"

  if check
    begin
      target_path = target_path.realpath
    rescue Errno::ENOENT
      raise OllamaChat::InvalidPathError,
        "#{target_path.to_s.inspect} does not exist"
    end
    case check
    when :file
      target_path.file? or
        raise OllamaChat::InvalidPathError,
          "#{target_path.to_s.inspect} is not a file"
    when :directory
      target_path.directory? or
        raise OllamaChat::InvalidPathError,
          "#{target_path.to_s.inspect} is not a directory"
    end
  elsif check == false
    target_path = target_path.cleanpath
    target_path.exist? and
      raise OllamaChat::InvalidPathError,
        "#{target_path.to_s.inspect} does already exist"
  end

  allowed_dirs, rest = Array(allowed).map do |p|
    Pathname.new(p).expand_path.cleanpath
  end.partition(&:directory?)

  unless rest.empty?
    warn "Ignoring non-directories: #{rest.map(&:to_path).map(&:inspect) * ', '} in assert_valid_path."
  end

  valid_path = allowed_dirs.any? do |allowed_dir|
    check_path = target_path
    check_path.directory? or check_path = check_path.dirname
    check_path.ascend.any? do |tp|
      tp == allowed_dir
    end
  end

  unless valid_path
    error = OllamaChat::InvalidPathError.new(
      "Path #{path.to_s.inspect} is not within allowed directories: " \
      "#{allowed_dirs&.map { _1.to_s.inspect }&.join(', ') || ?∅ }"
    )
    error.path = path
    raise error
  end

  target_path
end