From 05344fc85399902f431768d2738cd1d50083314f Mon Sep 17 00:00:00 2001 From: Tim Wade Date: Tue, 16 Jun 2020 10:54:35 -0700 Subject: [PATCH] Generate content security policy for non-HTML responses One feature of the content security policy DSL, though undocumented, is that it will not generate headers for non-HTML responses, even if a configuration is explicitly provided. While it may not seem obvious that anyone would want to send this header in an API response, Mozilla Observatory, for instance, recommends the following for API responses: `Content-Security-Policy: default-src 'none'; frame-ancestors 'none'` (source: https://observatory.mozilla.org/faq/) The Secure Headers gem also makes recommendations about the content security policy for API responses: https://github.com/github/secure_headers#api-configurations As such, this removes the HTML guard clause from the `ContentSecurityPolicy` middleware. --- actionpack/CHANGELOG.md | 3 +++ .../http/content_security_policy.rb | 7 ------- .../test/dispatch/content_security_policy_test.rb | 15 +++++++++++++++ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index d726b82e96663..0832663349d87 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,6 @@ +* Allow Content Security Policy DSL to generate for API responses. + *Tim Wade* + * When multiple domains are specified for a cookie, a domain will now be chosen only if it is equal to or is a superdomain of the request host. diff --git a/actionpack/lib/action_dispatch/http/content_security_policy.rb b/actionpack/lib/action_dispatch/http/content_security_policy.rb index e8cf1b95a5738..79c346af80f25 100644 --- a/actionpack/lib/action_dispatch/http/content_security_policy.rb +++ b/actionpack/lib/action_dispatch/http/content_security_policy.rb @@ -17,7 +17,6 @@ def call(env) request = ActionDispatch::Request.new env _, headers, _ = response = @app.call(env) - return response unless html_response?(headers) return response if policy_present?(headers) if policy = request.content_security_policy @@ -31,12 +30,6 @@ def call(env) end private - def html_response?(headers) - if content_type = headers[CONTENT_TYPE] - /html/.match?(content_type) - end - end - def header_name(request) if request.content_security_policy_report_only POLICY_REPORT_ONLY diff --git a/actionpack/test/dispatch/content_security_policy_test.rb b/actionpack/test/dispatch/content_security_policy_test.rb index 3d60dc1661fdf..2facbbf1e945b 100644 --- a/actionpack/test/dispatch/content_security_policy_test.rb +++ b/actionpack/test/dispatch/content_security_policy_test.rb @@ -377,6 +377,11 @@ class PolicyController < ActionController::Base content_security_policy_report_only only: :report_only + content_security_policy only: :api do |p| + p.default_src :none + p.frame_ancestors :none + end + def index head :ok end @@ -405,6 +410,10 @@ def no_policy head :ok end + def api + render json: {} + end + private def condition? params[:condition] == "true" @@ -421,6 +430,7 @@ def condition? get "/script-src", to: "policy#script_src" get "/style-src", to: "policy#style_src" get "/no-policy", to: "policy#no_policy" + get "/api", to: "policy#api" end end @@ -492,6 +502,11 @@ def test_generates_no_content_security_policy assert_nil response.headers["Content-Security-Policy-Report-Only"] end + def test_generates_api_security_policy + get "/api" + assert_policy "default-src 'none'; frame-ancestors 'none'" + end + private def assert_policy(expected, report_only: false) assert_response :success