Module: RailsMcpServer::PathValidator

Defined in:
lib/rails-mcp-server/utilities/path_validator.rb

Constant Summary collapse

SENSITIVE_PATTERNS =

Sensitive file patterns - files that should never be read or listed

[
  /\.env(\..*)?$/i,                    # .env, .env.local, .env.production
  /\.key$/i,                           # *.key files
  /\.pem$/i,                           # SSL certificates
  /\.crt$/i,                           # SSL certificates
  /\.p12$/i,                           # PKCS12 certificates
  /credentials\.yml(\.enc)?$/i,        # Rails credentials (encrypted or not)
  /secrets\.yml(\.enc)?$/i,            # Rails secrets
  /master\.key$/i,                     # Rails master key
  /config\/credentials/i,              # credentials directory
  /config\/secrets/i,                  # secrets directory
  /\.secret$/i,                        # generic secret files
  /password/i,                         # password files
  /\.ssh\//i,                          # SSH directory
  /id_rsa/i,                           # SSH keys
  /id_ed25519/i,                       # SSH keys
  /id_ecdsa/i,                         # SSH keys
  /\.gnupg\//i,                        # GPG directory
  /\.netrc$/i,                         # netrc credentials
  /\.pgpass$/i,                        # PostgreSQL passwords
  /database\.yml$/i,                   # Database configuration
  /storage\.yml$/i                     # Active Storage config (may contain keys)
].freeze
EXCLUDED_DIRECTORIES =

Directories that should be excluded from listing

%w[
  .git/
  .bundle/
  node_modules/
  vendor/bundle/
  vendor/cache/
  tmp/
  log/
  storage/
  .ruby-lsp/
].freeze

Class Method Summary collapse

Class Method Details

.excluded_directory?(path) ⇒ Boolean

Check if path is in excluded directory

Returns:

  • (Boolean)


96
97
98
# File 'lib/rails-mcp-server/utilities/path_validator.rb', line 96

def excluded_directory?(path)
  EXCLUDED_DIRECTORIES.any? { |dir| path.start_with?(dir) }
end

.filter_sensitive_files(files, project_root) ⇒ Object

Filter a list of files, removing sensitive ones



88
89
90
91
92
93
# File 'lib/rails-mcp-server/utilities/path_validator.rb', line 88

def filter_sensitive_files(files, project_root)
  files.reject do |file|
    relative_path = file.sub("#{project_root}/", "")
    sensitive_path?(relative_path)
  end
end

.safe_path?(path, project_root) ⇒ Boolean

Reject paths that resolve to project_root itself (e.g., ".", "..")

Returns:

  • (Boolean)


52
53
54
55
56
57
# File 'lib/rails-mcp-server/utilities/path_validator.rb', line 52

def safe_path?(path, project_root)
  return false if path.nil? || path.empty?

  expanded = File.expand_path(path, project_root)
  expanded.start_with?(project_root + "/")
end

.sensitive_path?(path) ⇒ Boolean

Check if a path matches sensitive patterns

Returns:

  • (Boolean)


46
47
48
49
# File 'lib/rails-mcp-server/utilities/path_validator.rb', line 46

def sensitive_path?(path)
  normalized = path.to_s.downcase
  SENSITIVE_PATTERNS.any? { |pattern| normalized.match?(pattern) }
end

.shell_escape(str) ⇒ Object

Escape a string for safe shell usage



70
71
72
# File 'lib/rails-mcp-server/utilities/path_validator.rb', line 70

def shell_escape(str)
  Shellwords.escape(str.to_s)
end

.valid_identifier?(name) ⇒ Boolean

Returns:

  • (Boolean)


74
75
76
77
78
# File 'lib/rails-mcp-server/utilities/path_validator.rb', line 74

def valid_identifier?(name)
  return false if name.nil? || name.empty?

  name.match?(/\A[a-zA-Z_][a-zA-Z0-9_]*(::[a-zA-Z_][a-zA-Z0-9_]*)*\z/)
end

.valid_table_name?(name) ⇒ Boolean

Validate table names (alphanumeric and underscores only)

Returns:

  • (Boolean)


81
82
83
84
85
# File 'lib/rails-mcp-server/utilities/path_validator.rb', line 81

def valid_table_name?(name)
  return false if name.nil? || name.empty?

  name.match?(/\A[a-zA-Z_][a-zA-Z0-9_]*\z/)
end

.validate_path(path, project_root) ⇒ Object

Validate and return safe absolute path, or nil if unsafe



60
61
62
63
64
65
66
67
# File 'lib/rails-mcp-server/utilities/path_validator.rb', line 60

def validate_path(path, project_root)
  return nil unless safe_path?(path, project_root)

  expanded = File.expand_path(path, project_root)
  return nil if sensitive_path?(expanded.sub(project_root + "/", ""))

  expanded
end