Class: Otto::Security::Config

Inherits:
Object
  • Object
show all
Defined in:
lib/otto/security/config.rb

Overview

Security configuration for Otto applications

This class manages all security-related settings including CSRF protection, input validation, trusted proxies, and security headers. Security features are disabled by default for backward compatibility.

Examples:

Basic usage

config = Otto::Security::Config.new
config.enable_csrf_protection!
config.add_trusted_proxy('10.0.0.0/8')

Custom limits

config = Otto::Security::Config.new
config.max_request_size = 5 * 1024 * 1024  # 5MB
config.max_param_depth = 16

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeConfig

Initialize security configuration with safe defaults

All security features are disabled by default to maintain backward compatibility with existing Otto applications.



33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/otto/security/config.rb', line 33

def initialize
  @csrf_protection = false
  @csrf_token_key = '_csrf_token'
  @csrf_header_key = 'HTTP_X_CSRF_TOKEN'
  @csrf_session_key = '_csrf_session_id'
  @max_request_size = 10 * 1024 * 1024 # 10MB
  @max_param_depth = 32
  @max_param_keys = 64
  @trusted_proxies = []
  @require_secure_cookies = false
  @security_headers = default_security_headers
  @input_validation = true
end

Instance Attribute Details

#csrf_header_keyObject

Returns the value of attribute csrf_header_key.



24
25
26
# File 'lib/otto/security/config.rb', line 24

def csrf_header_key
  @csrf_header_key
end

#csrf_protectionObject

Returns the value of attribute csrf_protection.



24
25
26
# File 'lib/otto/security/config.rb', line 24

def csrf_protection
  @csrf_protection
end

#csrf_session_keyObject

Returns the value of attribute csrf_session_key.



24
25
26
# File 'lib/otto/security/config.rb', line 24

def csrf_session_key
  @csrf_session_key
end

#csrf_token_keyObject

Returns the value of attribute csrf_token_key.



24
25
26
# File 'lib/otto/security/config.rb', line 24

def csrf_token_key
  @csrf_token_key
end

#input_validationObject

Returns the value of attribute input_validation.



24
25
26
# File 'lib/otto/security/config.rb', line 24

def input_validation
  @input_validation
end

#max_param_depthObject

Returns the value of attribute max_param_depth.



24
25
26
# File 'lib/otto/security/config.rb', line 24

def max_param_depth
  @max_param_depth
end

#max_param_keysObject

Returns the value of attribute max_param_keys.



24
25
26
# File 'lib/otto/security/config.rb', line 24

def max_param_keys
  @max_param_keys
end

#max_request_sizeObject

Returns the value of attribute max_request_size.



24
25
26
# File 'lib/otto/security/config.rb', line 24

def max_request_size
  @max_request_size
end

#require_secure_cookiesObject

Returns the value of attribute require_secure_cookies.



24
25
26
# File 'lib/otto/security/config.rb', line 24

def require_secure_cookies
  @require_secure_cookies
end

#security_headersObject

Returns the value of attribute security_headers.



24
25
26
# File 'lib/otto/security/config.rb', line 24

def security_headers
  @security_headers
end

#trusted_proxiesObject

Returns the value of attribute trusted_proxies.



24
25
26
# File 'lib/otto/security/config.rb', line 24

def trusted_proxies
  @trusted_proxies
end

Instance Method Details

#add_trusted_proxy(proxy) ⇒ void

This method returns an undefined value.

Add a trusted proxy server for accurate client IP detection

Only requests from trusted proxies will have their X-Forwarded-For and similar headers honored for IP detection. This prevents IP spoofing from untrusted sources.

Examples:

Add single proxy

config.add_trusted_proxy('10.0.0.1')

Add CIDR range

config.add_trusted_proxy('192.168.0.0/16')

Add multiple proxies

config.add_trusted_proxy(['10.0.0.1', '172.16.0.0/12'])

Parameters:

  • proxy (String, Array)

    IP address, CIDR range, or array of addresses

Raises:

  • (ArgumentError)

    if proxy is not a String or Array



92
93
94
95
96
97
98
99
100
101
# File 'lib/otto/security/config.rb', line 92

def add_trusted_proxy(proxy)
  case proxy
  when String
    @trusted_proxies << proxy
  when Array
    @trusted_proxies.concat(proxy)
  else
    raise ArgumentError, "Proxy must be a String or Array"
  end
end

#csrf_enabled?Boolean

Check if CSRF protection is currently enabled

Returns:

  • (Boolean)

    true if CSRF protection is enabled



70
71
72
# File 'lib/otto/security/config.rb', line 70

def csrf_enabled?
  @csrf_protection
end

#disable_csrf_protection!void

This method returns an undefined value.

Disable CSRF protection



63
64
65
# File 'lib/otto/security/config.rb', line 63

def disable_csrf_protection!
  @csrf_protection = false
end

#enable_csp!(policy = "default-src 'self'") ⇒ void

This method returns an undefined value.

Enable Content Security Policy (CSP) header

CSP helps prevent XSS attacks by controlling which resources can be loaded. The default policy only allows resources from the same origin.

Examples:

Custom policy

config.enable_csp!("default-src 'self'; script-src 'self' 'unsafe-inline'")

Parameters:

  • policy (String) (defaults to: "default-src 'self'")

    CSP policy string (default: “default-src ‘self’”)



198
199
200
# File 'lib/otto/security/config.rb', line 198

def enable_csp!(policy = "default-src 'self'")
  @security_headers['content-security-policy'] = policy
end

#enable_csrf_protection!void

This method returns an undefined value.

Enable CSRF (Cross-Site Request Forgery) protection

When enabled, Otto will:

  • Generate CSRF tokens for safe HTTP methods (GET, HEAD, OPTIONS, TRACE)

  • Validate CSRF tokens for unsafe methods (POST, PUT, DELETE, PATCH)

  • Automatically inject CSRF meta tags into HTML responses

  • Provide helper methods for forms and AJAX requests



56
57
58
# File 'lib/otto/security/config.rb', line 56

def enable_csrf_protection!
  @csrf_protection = true
end

#enable_frame_protection!(option = 'SAMEORIGIN') ⇒ void

This method returns an undefined value.

Enable X-Frame-Options header to prevent clickjacking

Parameters:

  • option (String) (defaults to: 'SAMEORIGIN')

    Frame options: ‘DENY’, ‘SAMEORIGIN’, or ‘ALLOW-FROM uri’



206
207
208
# File 'lib/otto/security/config.rb', line 206

def enable_frame_protection!(option = 'SAMEORIGIN')
  @security_headers['x-frame-options'] = option
end

#enable_hsts!(max_age: 31536000, include_subdomains: true) ⇒ void

This method returns an undefined value.

Enable HTTP Strict Transport Security (HSTS) header

HSTS forces browsers to use HTTPS for all future requests to this domain. WARNING: This can make your domain inaccessible if HTTPS is not properly configured. Only enable this when you’re certain HTTPS is working correctly.

Parameters:

  • max_age (Integer) (defaults to: 31536000)

    Maximum age in seconds (default: 1 year)

  • include_subdomains (Boolean) (defaults to: true)

    Apply to all subdomains (default: true)



182
183
184
185
186
# File 'lib/otto/security/config.rb', line 182

def enable_hsts!(max_age: 31536000, include_subdomains: true)
  hsts_value = "max-age=#{max_age}"
  hsts_value += "; includeSubDomains" if include_subdomains
  @security_headers['strict-transport-security'] = hsts_value
end

#generate_csrf_token(session_id = nil) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/otto/security/config.rb', line 138

def generate_csrf_token(session_id = nil)
  base = session_id || 'no-session'
  token = SecureRandom.hex(32)
  hash_input = base + ':' + token
  signature = Digest::SHA256.hexdigest(hash_input)
  csrf_token = "#{token}:#{signature}"

  puts "=== CSRF Generation ==="
  puts "hash_input: #{hash_input.inspect}"
  puts "signature: #{signature}"
  puts "csrf_token: #{csrf_token}"

  csrf_token
end

#get_or_create_session_id(request) ⇒ Object



224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/otto/security/config.rb', line 224

def get_or_create_session_id(request)
  # Try existing sources first
  session_id = extract_existing_session_id(request)

  # Create and persist if none found
  if session_id.nil? || session_id.empty?
    session_id = SecureRandom.hex(16)
    store_session_id(request, session_id)
  end

  session_id
end

#set_custom_headers(headers) ⇒ void

This method returns an undefined value.

Set custom security headers

Examples:

config.set_custom_headers({
  'permissions-policy' => 'geolocation=(), microphone=()',
  'cross-origin-opener-policy' => 'same-origin'
})

Parameters:

  • headers (Hash)

    Hash of header name => value pairs



220
221
222
# File 'lib/otto/security/config.rb', line 220

def set_custom_headers(headers)
  @security_headers.merge!(headers)
end

#trusted_proxy?(ip) ⇒ Boolean

Check if an IP address is from a trusted proxy

Parameters:

  • ip (String)

    IP address to check

Returns:

  • (Boolean)

    true if the IP is from a trusted proxy



107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/otto/security/config.rb', line 107

def trusted_proxy?(ip)
  return false if @trusted_proxies.empty?

  @trusted_proxies.any? do |proxy|
    case proxy
    when String
      ip == proxy || ip.start_with?(proxy)
    when Regexp
      proxy.match?(ip)
    else
      false
    end
  end
end

#validate_request_size(content_length) ⇒ Boolean

Validate that a request size is within acceptable limits

Parameters:

  • content_length (String, Integer, nil)

    Content-Length header value

Returns:

  • (Boolean)

    true if request size is acceptable

Raises:



127
128
129
130
131
132
133
134
135
136
# File 'lib/otto/security/config.rb', line 127

def validate_request_size(content_length)
  return true if content_length.nil?

  size = content_length.to_i
  if size > @max_request_size
    raise Otto::Security::RequestTooLargeError,
          "Request size #{size} exceeds maximum #{@max_request_size}"
  end
  true
end

#verify_csrf_token(token, session_id = nil) ⇒ Object



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/otto/security/config.rb', line 153

def verify_csrf_token(token, session_id = nil)
  return false if token.nil? || token.empty?

  token_part, signature = token.split(':')
  return false if token_part.nil? || signature.nil?

  base = session_id || 'no-session'
  hash_input = "#{base}:#{token_part}"
  expected_signature = Digest::SHA256.hexdigest(hash_input)
  comparison_result = secure_compare(signature, expected_signature)

  puts "=== CSRF Verification ==="
  puts "hash_input: #{hash_input.inspect}"
  puts "received_signature: #{signature}"
  puts "expected_signature: #{expected_signature}"
  puts "match: #{comparison_result}"

  comparison_result
end