Class: Sxn::Templates::TemplateSecurity

Inherits:
Object
  • Object
show all
Defined in:
lib/sxn/templates/template_security.rb

Overview

TemplateSecurity provides security validation and sanitization for templates. It ensures that templates cannot execute arbitrary code, access the filesystem, or perform other potentially dangerous operations.

Security Features:

  • Whitelist-based variable validation

  • Content sanitization to prevent injection

  • Path traversal prevention

  • Size and complexity limits

  • Execution time limits

Constant Summary collapse

MAX_TEMPLATE_DEPTH =

Maximum allowed template complexity (nested structures)

10
MAX_VARIABLE_COUNT =

Maximum number of variables allowed in a template

1000
DANGEROUS_PATTERNS =

Dangerous patterns that should not appear in templates

[
  # Ruby code execution - look for actual method calls, not just words
  /\b(?:eval|exec|system|spawn|fork)\s*[\(\[]/,
  /\b(?:require|load|autoload)\s*[\(\['"]/,

  # File/IO operations - look for actual usage, not just the words
  /\b(?:File|Dir|IO|Kernel|Process|Thread)\s*\./,
  /\b(?:File|Dir|IO)\.(?:open|read|write|delete)/,

  # Shell injection patterns - be very specific
  # Removed backtick check as it causes too many false positives with markdown
  # Liquid doesn't execute Ruby code directly anyway
  /%x\{[^}]*\}/, # %x{} command execution
  /\bsystem\s*\(/, # Direct system calls
  /%x[{\[]/, # Alternative command execution syntax (removed \b since % is not a word char)

  # Web security patterns
  /<script\b[^>]*>/i, # Script tags
  /javascript:/i, # JavaScript protocols
  /on\w+\s*=/i, # Event handlers

  # Liquid-specific dangerous patterns
  /\{\{.*\|\s*(?:eval|exec|system)\s*\}\}/, # Piped to dangerous filters
  /\{\{\s*(?:eval|exec|system)\s*\(/, # Direct calls to dangerous functions
  /\{%\s*(?:eval|exec)\b/, # Liquid eval/exec commands

  # Ruby metaprogramming that could be dangerous
  /\bsend\s*\(/,
  /\b__send__\s*\(/,
  /\bpublic_send\s*\(/,
  /\binstance_eval\b/,
  /\bclass_eval\b/,
  /\bmodule_eval\b/,

  # File system access patterns
  /\{\{\s*file\.(?:read|write|delete)\b/i,
  /\{%\s*(?:write_file|delete)\b/i,
  /\{\{\s*delete\s*\(/i
].freeze
ALLOWED_VARIABLE_NAMESPACES =

Whitelisted variable namespaces

%w[
  session
  git
  project
  environment
  user
  timestamp
  ruby
  rails
  node
  database
  os
].freeze
SAFE_FILTERS =

Whitelisted filters (subset of Liquid’s standard filters)

%w[
  upcase downcase capitalize
  strip lstrip rstrip
  size length
  first last
  join split
  sort sort_natural reverse
  uniq compact
  date
  default
  escape escape_once
  truncate truncatewords
  replace replace_first
  remove remove_first
  plus minus times divided_by modulo
  abs ceil floor round
  at_least at_most
].freeze

Instance Method Summary collapse

Constructor Details

#initializeTemplateSecurity

Returns a new instance of TemplateSecurity.



98
99
100
# File 'lib/sxn/templates/template_security.rb', line 98

def initialize
  @validation_cache = {}
end

Instance Method Details

#clear_cache!Object

Clear validation cache (useful for testing)



168
169
170
# File 'lib/sxn/templates/template_security.rb', line 168

def clear_cache!
  @validation_cache.clear
end

#safe_filter?(filter_name) ⇒ Boolean

Validate that a filter is safe to use

Parameters:

  • filter_name (String)

    Name of the filter to validate

Returns:

  • (Boolean)

    true if filter is safe



163
164
165
# File 'lib/sxn/templates/template_security.rb', line 163

def safe_filter?(filter_name)
  SAFE_FILTERS.include?(filter_name.to_s)
end

#sanitize_variables(variables) ⇒ Hash

Sanitize template variables to remove potentially dangerous content

Parameters:

  • variables (Hash)

    Variables to sanitize

Returns:

  • (Hash)

    Sanitized variables



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/sxn/templates/template_security.rb', line 138

def sanitize_variables(variables)
  # First check total variable count before processing
  total_variables = count_total_variables(variables)
  if total_variables > MAX_VARIABLE_COUNT
    raise Errors::TemplateSecurityError,
          "Too many variables: #{total_variables} exceeds limit of #{MAX_VARIABLE_COUNT}"
  end

  sanitized = {}

  variables.each do |key, value|
    sanitized_key = sanitize_variable_key(key)
    next unless valid_variable_namespace?(sanitized_key)

    sanitized_value = sanitize_variable_value(value, depth: 0)
    sanitized[sanitized_key] = sanitized_value
  end

  sanitized
end

#validate_template(template_content, variables = {}) ⇒ Boolean

Validate template content for security issues

Parameters:

  • template_content (String)

    The template content to validate

  • variables (Hash) (defaults to: {})

    Variables that will be used with the template

Returns:

  • (Boolean)

    true if template is safe

Raises:

  • (TemplateSecurityError)

    if security violations are found



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/sxn/templates/template_security.rb', line 108

def validate_template(template_content, variables = {})
  # Check cache first
  cache_key = generate_cache_key(template_content, variables)
  if @validation_cache.key?(cache_key)
    cached_result = @validation_cache[cache_key]
    raise Errors::TemplateSecurityError, "Cached validation error for template" if cached_result == false

    # Re-raise cached error without re-validating

    return cached_result

  end

  begin
    result = validate_template_content(template_content)
    validate_template_variables(variables)
    validate_template_complexity(template_content)

    @validation_cache[cache_key] = result
    result
  rescue Errors::TemplateSecurityError => e
    @validation_cache[cache_key] = false
    raise e
  end
end

#validate_template_content(template_content) ⇒ Object

Validate template content for dangerous patterns (public version for tests)



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/sxn/templates/template_security.rb', line 173

def validate_template_content(template_content)
  DANGEROUS_PATTERNS.each do |pattern|
    next unless template_content.match?(pattern)

    raise Errors::TemplateSecurityError,
          "Template contains dangerous pattern: #{pattern.source}"
  end

  # Check for path traversal attempts
  if template_content.include?("../") || template_content.include?("..\\")
    raise Errors::TemplateSecurityError,
          "Template contains path traversal attempt"
  end

  # Check for file system access attempts - be more specific
  # Look for actual File/Dir method calls, not just the words
  if template_content.match?(/\{\{\s*.*(?:File|Dir|IO)\.(?:read|write|delete|create|open).*\s*\}\}/)
    raise Errors::TemplateSecurityError,
          "Template attempts file system access"
  end

  true
end

#validate_template_path(template_path) ⇒ Boolean

Validate template path for security issues

Parameters:

  • template_path (String, Pathname)

    Path to the template file

Returns:

  • (Boolean)

    true if path is safe

Raises:



394
395
396
397
398
399
400
401
402
403
404
405
406
407
# File 'lib/sxn/templates/template_security.rb', line 394

def validate_template_path(template_path)
  path = Pathname.new(template_path)

  # Check for path traversal attempts
  normalized_path = path.expand_path.to_s
  if normalized_path.include?("..") || normalized_path.include?("~")
    raise Errors::TemplateSecurityError, "Template path contains traversal attempt: #{template_path}"
  end

  # Check that the file exists and is readable
  raise Errors::TemplateSecurityError, "Template path is not accessible: #{template_path}" unless path.exist? && path.readable?

  true
end