Module: RailVerdict::PathSafety

Defined in:
lib/rail_verdict/path_safety.rb

Overview

Symlink-aware path containment used by CLI and MCP surfaces.

Class Method Summary collapse

Class Method Details

.assert_contained!(root, path, label) ⇒ Object

Raises:



33
34
35
36
37
# File 'lib/rail_verdict/path_safety.rb', line 33

def assert_contained!(root, path, label)
  raise UsageError, "#{label} escapes working directory: #{path}" unless contained?(root, path)

  File.expand_path(path.to_s, File.realpath(root))
end

.contained?(root, path) ⇒ Boolean

True when path (absolute or root-relative) resolves, through its deepest EXISTING ancestor, to inside root. Trailing nonexistent segments are checked lexically against the resolved ancestor so a symlink cannot escape after creation either.

Returns:

  • (Boolean)


12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/rail_verdict/path_safety.rb', line 12

def contained?(root, path)
  root_real = File.realpath(root)
  target = File.expand_path(path.to_s, root_real)

  existing = target
  until File.exist?(existing) || File.symlink?(existing)
    parent = File.dirname(existing)
    return false if parent == existing

    existing = parent
  end
  real_existing = File.realpath(existing)
  return false unless real_existing == root_real || real_existing.start_with?(root_real + File::SEPARATOR)

  # Remaining (nonexistent) tail must not traverse upward.
  tail = target[real_existing.length..].to_s
  !tail.split(File::SEPARATOR).include?("..")
rescue Errno::ENOENT, Errno::EACCES, Errno::ELOOP
  false
end