Class: Tina4::SecurityHeadersMiddleware

Inherits:
Object
  • Object
show all
Defined in:
lib/tina4/middleware.rb

Overview

SecurityHeadersMiddleware -- injects security headers on every response. Config via env:

TINA4_FRAME_OPTIONS       — X-Frame-Options (default: SAMEORIGIN)
TINA4_HSTS                — Strict-Transport-Security max-age (default: "" = off)
TINA4_CSP                 — Content-Security-Policy (default: "default-src 'self'")
TINA4_REFERRER_POLICY     — Referrer-Policy (default: strict-origin-when-cross-origin)
TINA4_PERMISSIONS_POLICY  — Permissions-Policy (default: camera=(), microphone=(), geolocation=())

Class Method Summary collapse

Class Method Details

.attachObject

Register this middleware in the default chain (secure-by-default).

Unlike CSRF (opt-in via TINA4_CSRF) this is UNCONDITIONAL: a default app ships the security headers with no opt-in -- the SECHDR-DEC-01 posture that closes the SECHDR-OFF-BY-DEFAULT gap (the middleware existed with good defaults but was never registered). Idempotent (Middleware.use de-dupes). The framework calls it once at boot (Tina4.initialize!). Returns true.



787
788
789
790
# File 'lib/tina4/middleware.rb', line 787

def attach
  Tina4::Middleware.use(self)
  true
end

.before_security(request, response) ⇒ Object



792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
# File 'lib/tina4/middleware.rb', line 792

def before_security(request, response)
  response.headers["X-Frame-Options"] = ENV["TINA4_FRAME_OPTIONS"] || "SAMEORIGIN"
  response.headers["X-Content-Type-Options"] = "nosniff"

  # HSTS is HTTPS-only (SECHDR-DEC-02): a downgrade-protection header on a
  # plain-HTTP response is inert at best and ships a bad max-age on an
  # unencrypted scheme at worst. Emit it ONLY when TINA4_HSTS is set AND the
  # request is HTTPS -- Request.secure_scheme? honours x-forwarded-proto
  # (first hop) then rack.url_scheme, the same source of truth the session
  # cookie's Secure flag uses. Defensive env lookup keeps a non-Request from
  # turning every response into a 500 now that this runs on every request.
  hsts = ENV["TINA4_HSTS"] || ""
  env = request.respond_to?(:env) ? request.env : {}
  if !hsts.empty? && Tina4::Request.secure_scheme?(env)
    response.headers["Strict-Transport-Security"] = "max-age=#{hsts}; includeSubDomains"
  end

  response.headers["Content-Security-Policy"] = ENV["TINA4_CSP"] || "default-src 'self'"
  response.headers["Referrer-Policy"] = ENV["TINA4_REFERRER_POLICY"] || "strict-origin-when-cross-origin"
  response.headers["X-XSS-Protection"] = "0"
  response.headers["Permissions-Policy"] = ENV["TINA4_PERMISSIONS_POLICY"] || "camera=(), microphone=(), geolocation=()"

  [request, response]
end