Module: Otto::Security::Core
- Included in:
- Otto
- Defined in:
- lib/otto/security/core.rb
Overview
Core security configuration methods included in the Otto class. Provides the public API for enabling and configuring security features.
Instance Method Summary collapse
-
#add_auth_strategy(name, strategy) ⇒ Object
Add an authentication strategy with a registered name.
-
#add_rate_limit_rule(name, options) ⇒ Object
Add a custom rate limiting rule.
-
#add_trusted_proxy(proxy) ⇒ Object
Add a trusted proxy server for accurate client IP detection.
-
#enable_csp!(policy = "default-src 'self'") ⇒ Object
Enable Content Security Policy (CSP) header to prevent XSS attacks.
-
#enable_csp_emission!(eager: false, development_mode: nil) ⇒ void
Mount Otto::Security::CSP::EmitMiddleware so nonce-based CSP headers are applied to responses by the framework instead of hand-rolled in each app.
-
#enable_csp_reporting!(report_uri, endpoint_url: nil) {|report| ... } ⇒ void
Enable turnkey Content Security Policy violation reporting.
-
#enable_csp_with_nonce!(debug: false) ⇒ Object
Enable Content Security Policy (CSP) with nonce support for dynamic header generation.
-
#enable_csrf_protection! ⇒ Object
Enable CSRF protection for POST, PUT, DELETE, and PATCH requests.
-
#enable_frame_protection!(option = 'SAMEORIGIN') ⇒ Object
Enable X-Frame-Options header to prevent clickjacking attacks.
-
#enable_hsts!(max_age: 31_536_000, include_subdomains: true) ⇒ Object
Enable HTTP Strict Transport Security (HSTS) header.
-
#enable_rate_limiting!(options = {}) ⇒ Object
Enable rate limiting to protect against abuse and DDoS attacks.
-
#enable_request_validation! ⇒ Object
Enable request validation including input sanitization, size limits, and protection against XSS and SQL injection attacks.
-
#set_security_headers(headers) ⇒ Object
Set custom security headers that will be added to all responses.
-
#trust_no_proxies! ⇒ void
Assert that NO proxy is trusted: every peer gets env = false, forwarded client-IP chains are ignored, and forwarded host/scheme/port carriers are stripped so Rack::Request#host resolves only from the Host header.
Instance Method Details
#add_auth_strategy(name, strategy) ⇒ Object
Add an authentication strategy with a registered name
This is the primary public API for registering authentication strategies.
The name you provide here will be available as strategy_result.strategy_name
in your application code, making it easy to identify which strategy authenticated
the current request.
Also available via Otto::Security::Configurator for consolidated security config.
268 269 270 271 272 273 274 275 276 277 278 279 |
# File 'lib/otto/security/core.rb', line 268 def add_auth_strategy(name, strategy) ensure_not_frozen! # Ensure auth_config is initialized (handles edge case where it might be nil) @auth_config = { auth_strategies: {}, default_auth_strategy: 'noauth' } if @auth_config.nil? # Strict mode: Detect strategy name collisions if @auth_config[:auth_strategies].key?(name) raise ArgumentError, "Authentication strategy '#{name}' is already registered" end @auth_config[:auth_strategies][name] = strategy end |
#add_rate_limit_rule(name, options) ⇒ Object
Add a custom rate limiting rule.
63 64 65 66 |
# File 'lib/otto/security/core.rb', line 63 def add_rate_limit_rule(name, ) ensure_not_frozen! @security_config.rate_limiting_config[:custom_rules][name.to_s] = end |
#add_trusted_proxy(proxy) ⇒ Object
Add a trusted proxy server for accurate client IP detection. Only requests from trusted proxies will have their forwarded headers honored.
75 76 77 78 |
# File 'lib/otto/security/core.rb', line 75 def add_trusted_proxy(proxy) ensure_not_frozen! @security_config.add_trusted_proxy(proxy) end |
#enable_csp!(policy = "default-src 'self'") ⇒ Object
Enable Content Security Policy (CSP) header to prevent XSS attacks. The default policy only allows resources from the same origin.
130 131 132 133 |
# File 'lib/otto/security/core.rb', line 130 def enable_csp!(policy = "default-src 'self'") ensure_not_frozen! @security_config.enable_csp!(policy) end |
#enable_csp_emission!(eager: false, development_mode: nil) ⇒ void
This method returns an undefined value.
Mount Otto::Security::CSP::EmitMiddleware so nonce-based CSP headers are applied to responses by the framework instead of hand-rolled in each app.
It is a passive backstop: it emits a nonce CSP only for responses that would otherwise ship without one, and never clobbers a policy a route already set. Enable nonce-CSP via #enable_csp_with_nonce! for it to emit anything — until then the middleware is INERT (a transparent pass-through), NOT an error. The two may be enabled in either order: both read the same security config, so mounting the backstop first and enabling nonce-CSP later works. Enable-order independence is why this does not raise when nonce-CSP is off.
By DEFAULT it is emit-if-consumed — it emits only when the request actually consumed a nonce (a view called Request#csp_nonce). This is the only safe blanket default: a nonce-only policy on a page that never stamped the nonce blocks every script.
181 182 183 184 185 186 |
# File 'lib/otto/security/core.rb', line 181 def enable_csp_emission!(eager: false, development_mode: nil) ensure_not_frozen! return if @middleware.includes?(Otto::Security::CSP::EmitMiddleware) @middleware.add(Otto::Security::CSP::EmitMiddleware, eager: eager, development_mode: development_mode) end |
#enable_csp_reporting!(report_uri, endpoint_url: nil) {|report| ... } ⇒ void
This method returns an undefined value.
Enable turnkey Content Security Policy violation reporting.
This is the receiving half of Otto's CSP support. It:
- Configures the report path (
config.csp_report_uri = report_uri), so areport-uridirective is appended to every emitted CSP policy (static #enable_csp! and nonce #enable_csp_with_nonce! alike). - Registers your violation callback (if a block is given).
- Injects Otto::Security::CSP::ReportMiddleware so browser POSTs to the report path are received, parsed, and dispatched to the callback — always answered with 204 and never touching your routes.
The middleware is pinned to run OUTERMOST (ahead of CSRF and every other middleware), so it short-circuits report POSTs before CSRF validation — browsers can post reports without a CSRF token. This holds regardless of the order in which you enable security features. The one thing that runs ahead of it is IPPrivacyMiddleware, pinned to the outer :entrypoint tier so nothing observes a raw client IP; being a pass-through, it cannot affect the short-circuit.
SECURITY / DoS: running outermost also means the receiver sits ahead of rate limiting (rate limiting is inner middleware). This is intentional — a public, unauthenticated report endpoint cannot depend on CSRF, session, or per-client throttling state — but it means a client can POST reports up to the 64 KiB body cap and invoke your callback on each one. Keep the callback cheap and bounded (sample or aggregate; avoid unbounded synchronous I/O), and put request-rate control for this path at the edge (reverse proxy / CDN / WAF) rather than expecting Otto to throttle it.
To (re)assign the callback later without touching the wiring, use the
config primitive directly: otto.security_config.on_csp_violation { ... }.
For modern browsers (which have deprecated report-uri), also pass
endpoint_url: — an ABSOLUTE URL whose path is report_uri. Otto then
emits a report-to directive plus a Reporting-Endpoints header so those
browsers deliver application/reports+json to the same receiver.
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 |
# File 'lib/otto/security/core.rb', line 237 def enable_csp_reporting!(report_uri, endpoint_url: nil, &block) ensure_not_frozen! @security_config.csp_report_uri = report_uri @security_config.csp_report_to_url = endpoint_url unless endpoint_url.nil? @security_config.on_csp_violation(&block) if block return if @middleware.includes?(Otto::Security::CSP::ReportMiddleware) # Pin OUTERMOST so it intercepts report POSTs ahead of CSRF regardless of # the order security features are enabled in. add_with_position fires the # stack's on_change callback, which rebuilds @app (wired in # Otto#initialize_core_state) — no explicit build_app! needed. @middleware.add_with_position(Otto::Security::CSP::ReportMiddleware, position: :outermost) end |
#enable_csp_with_nonce!(debug: false) ⇒ Object
Enable Content Security Policy (CSP) with nonce support for dynamic header generation. This enables the res.send_csp_headers response helper method.
151 152 153 154 |
# File 'lib/otto/security/core.rb', line 151 def enable_csp_with_nonce!(debug: false) ensure_not_frozen! @security_config.enable_csp_with_nonce!(debug: debug) end |
#enable_csrf_protection! ⇒ Object
Enable CSRF protection for POST, PUT, DELETE, and PATCH requests. This will automatically add CSRF tokens to HTML forms and validate them on unsafe HTTP methods.
16 17 18 19 20 21 22 |
# File 'lib/otto/security/core.rb', line 16 def enable_csrf_protection! ensure_not_frozen! return if @middleware.includes?(Otto::Security::Middleware::CSRFMiddleware) @security_config.enable_csrf_protection! use Otto::Security::Middleware::CSRFMiddleware end |
#enable_frame_protection!(option = 'SAMEORIGIN') ⇒ Object
Enable X-Frame-Options header to prevent clickjacking attacks.
140 141 142 143 |
# File 'lib/otto/security/core.rb', line 140 def enable_frame_protection!(option = 'SAMEORIGIN') ensure_not_frozen! @security_config.enable_frame_protection!(option) end |
#enable_hsts!(max_age: 31_536_000, include_subdomains: true) ⇒ Object
Enable HTTP Strict Transport Security (HSTS) header. WARNING: This can make your domain inaccessible if HTTPS is not properly configured. Only enable this when you're certain HTTPS is working correctly.
119 120 121 122 |
# File 'lib/otto/security/core.rb', line 119 def enable_hsts!(max_age: 31_536_000, include_subdomains: true) ensure_not_frozen! @security_config.enable_hsts!(max_age: max_age, include_subdomains: include_subdomains) end |
#enable_rate_limiting!(options = {}) ⇒ Object
Enable rate limiting to protect against abuse and DDoS attacks. This will automatically add rate limiting rules based on client IP.
45 46 47 48 49 50 51 52 |
# File 'lib/otto/security/core.rb', line 45 def enable_rate_limiting!( = {}) ensure_not_frozen! return if @middleware.includes?(Otto::Security::Middleware::RateLimitMiddleware) Otto::Security::RateLimiting.ensure_available! @security.configure_rate_limiting() use Otto::Security::Middleware::RateLimitMiddleware end |
#enable_request_validation! ⇒ Object
Enable request validation including input sanitization, size limits, and protection against XSS and SQL injection attacks.
29 30 31 32 33 34 35 |
# File 'lib/otto/security/core.rb', line 29 def enable_request_validation! ensure_not_frozen! return if @middleware.includes?(Otto::Security::Middleware::ValidationMiddleware) @security_config.input_validation = true use Otto::Security::Middleware::ValidationMiddleware end |
#set_security_headers(headers) ⇒ Object
Set custom security headers that will be added to all responses. These merge with the default security headers.
106 107 108 109 |
# File 'lib/otto/security/core.rb', line 106 def set_security_headers(headers) ensure_not_frozen! @security_config.security_headers.merge!(headers) end |
#trust_no_proxies! ⇒ void
This method returns an undefined value.
Assert that NO proxy is trusted: every peer gets env = false, forwarded client-IP chains are ignored, and forwarded host/scheme/port carriers are stripped so Rack::Request#host resolves only from the Host header.
Equivalent to passing trusted_proxies: :none to Otto.new. Distinct
from configuring nothing, which asserts nothing (tri-state, #228).
92 93 94 95 |
# File 'lib/otto/security/core.rb', line 92 def trust_no_proxies! ensure_not_frozen! @security_config.trust_no_proxies! end |