Official integration with the SecureHeaders gem

Currently, Hanami uses a bespoke implementation for things like CSP and other security related headers. Unfortunately, this implementation is somewhat handicapped compared the secure-headers gem for the following reasons.

  1. It is not possible / easy to override CSP directives per action, and other headers per action
  2. Also, its does not offer the ability to “compile” a CSP into a smaller policy like secure-headers to a smaller, more efficient representation
  3. I would also argue that secure-headers is more secure by default, by forcing explicit Opt-Out behavior, and
  4. It is more battle tested

So, instead of hand-rolling a custom security configuration, I propose that we add official integration for SecureHeaders.

Example

For reference, here is how we use secure-headers in our apps (overriding the Hanami implementation)

      # apps/web/application.rb
      SecureHeaders::Configuration.override(:web) do |config|

        config.csp = Security::CSP.with(
          :self,
          :typekit,
          :fullstory,
        )
      end
# apps/web/controllers/security

module Web
  module Controllers
    # Security context for all controllers
    module Security
      def self.included(action)
        action.class_eval do
          include SecureHeaders

          # Use the :web configuration from application.rb
          before { use_secure_headers_override(:web) }
        end
      end
    end
  end
end

Proposed API

We could provide a thin wrapper over the SecureHeaders::Configuration class which would look something like:

module Web
  # Application config for web
  class Application < Hanami::Application
    configure do
      # will create an overridden config for the Web app
      security do
        x_frame_options "DENY"
        cookies secure: true
      end
    end
  end
end

@ianks Thanks for your inputs, please let me reply one by one:

You can change Hanami::Action#headers to override CSP or any other response header on action basis:

def call(params)
  # override default value
  headers["Content-Security-Policy"] = "..."
  # to disable it for the current action
  headers.delete("Content-Security-Policy")
end

I agree with you here.

That is a matter of changing the default generated values for new Hanami apps.

True.


I’m always skeptical when a technical solution implies to add a gem as direct dependency. Not because of NIH, but because CSP it’s a matter of response HTTP headers, we have the tools to manage them with the current code that we have.

We can argue if the API it’s ergonomic or not, and I agree, we can do better than Hanami 1.x. Indeed, I tried to enhance it for 2.0. Would you please have a look at it?

Here’s the gist of it. Without any setting, it returns a default CSP header.

# config/application.rb
module Soundeck
  class Application < Hanami::Application
  end
end

But you can override specific settings like adding a new source for style and remove plugin_types at all.

module Soundeck
  class Application < Hanami::Application
    config.security.content_security_policy[:style_src] += " https://my.cdn.example"
    config.security.content_security_policy[:plugin_types] = nil
  end
end

In this case the idea is to not manage the entire CSP blob, but only the relevant bits. In this way it’s easier to deal with.

There is also another important reason. With Hanami 1.x we had the generated code (in apps/web/application.rb), if we wanted to change the default settings for CSP, that required a manual intervention by the developer. With Hanami 2.0, because the configuration has default settings for CSP, unless the developer did specific overrides to a sub-part of CSP, we can change the value with a new Hanami release, and your app is upgraded.

What do you think?