Class: Otto::Security::CSP::ReportMiddleware

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

Overview

Rack middleware that receives browser-posted Content-Security-Policy violation reports and dispatches them to an application callback.

This is the receiving half of Otto's CSP support; the emitting half is Otto::Security::Config#generate_nonce_csp / Response#send_csp_headers (and the static Otto::Security::Config#enable_csp!). When a report URI is configured, the emitted policy carries a report-uri directive pointing here.

Behavior (all mandatory for a public, unauthenticated receiver):

  • INERT unless Otto::Security::Config#csp_report_uri is set. When it is not configured the middleware is a transparent pass-through.
  • Only intercepts a POST whose path matches the configured report URI. Everything else (other paths, other methods) passes through untouched. The match is on the full request path (mount prefix included), since the report URI is the site-absolute path the browser POSTs to.
  • Short-circuits BEFORE inner middleware, so CSRF, auth, and rate limiting never see the request. This is why browsers can POST reports with no CSRF token: the report never reaches the CSRF middleware. Otto::Security::Core#enable_csp_reporting! pins this middleware OUTERMOST (via the :outermost stack position), so the guarantee holds regardless of the order security features are enabled in. Only IPPrivacyMiddleware (pinned to the outer :entrypoint tier) runs ahead of it, and it is a pass-through. The flip side is that reports also bypass rate limiting — see the DoS note on Otto::Security::Core#enable_csp_reporting!; keep callbacks cheap.
  • Enforces a hard MAX_BODY_BYTES body cap. Oversized bodies are detected with a cap + 1 read and skipped WITHOUT parsing, so a hostile client cannot force large allocations against a public endpoint.
  • Parses both wire formats via Parser and invokes the registered callback once per normalized report.
  • NEVER raises to the client and always responds 204 No Content (browsers ignore the body). A throwing callback is isolated by Otto::Security::Config#dispatch_csp_violation.

Constant Summary collapse

MAX_BODY_BYTES =

Hard cap on the request body we are willing to read/parse. Browsers send small JSON documents; anything larger is abuse and is dropped.

64 * 1024

Instance Method Summary collapse

Constructor Details

#initialize(app, config = nil) ⇒ ReportMiddleware

64 KiB



50
51
52
53
# File 'lib/otto/security/csp/report_middleware.rb', line 50

def initialize(app, config = nil)
  @app    = app
  @config = config || Otto::Security::Config.new
end

Instance Method Details

#call(env) ⇒ Object



55
56
57
58
59
# File 'lib/otto/security/csp/report_middleware.rb', line 55

def call(env)
  return @app.call(env) unless report_request?(env)

  receive_report(env)
end