Skip to content

Commit

Permalink
infer default policies when incomplete CSP configuration is supplied.
Browse files Browse the repository at this point in the history
  • Loading branch information
oreoshake committed Aug 18, 2016
1 parent 7600b90 commit 3225ed6
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 5 deletions.
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,32 @@ use SecureHeaders::Middleware

## Default values

All headers except for PublicKeyPins have a default value. See the [corresponding classes for their defaults](https://github.com/twitter/secureheaders/tree/master/lib/secure_headers/headers).
All headers except for PublicKeyPins have a default value. See the [corresponding classes for their defaults](https://github.com/twitter/secureheaders/tree/master/lib/secure_headers/headers). The default set of headers is:

```
Content-Security-Policy: default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'
Strict-Transport-Security: max-age=631138519
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Frame-Options: sameorigin
X-Permitted-Cross-Domain-Policies: none
X-Xss-Protection: 1; mode=block
```

### Default CSP

By default, the above CSP will be applied to all requests. If you **only** want to set a Report-Only header, opt-out of the default enforced header for clarity. The configuration will assume that if you only supply `csp_report_only` that you intended to opt-out of `csp` but that's for the sake of backwards compatibility and it will be removed in the future.

```ruby
Configuration.default do |config|
config.csp = SecureHeaders::OPT_OUT # If this line is omitted, we will assume you meant to opt out.
config.csp_report_only = {
default_src: %w('self')
}
end
```

If **

## Named overrides

Expand Down
15 changes: 13 additions & 2 deletions lib/secure_headers/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,16 @@ def csp=(new_csp)
end
end

# Configures the Content-Security-Policy-Report-Only header. `new_csp` cannot
# contain `report_only: false` or an error will be raised.
#
# NOTE: if csp has not been configured/has the default value when
# configuring csp_report_only, the code will assume you mean to only use
# report-only mode and you will be opted-out of enforce mode.
def csp_report_only=(new_csp)
@csp_report_only = begin
if new_csp.is_a?(ContentSecurityPolicyConfig)
ContentSecurityPolicyReportOnlyConfig.new(new_csp.to_h)
if new_csp.is_a?(ContentSecurityPolicyConfig)
new_csp.make_report_only
elsif new_csp.respond_to?(:opt_out?)
new_csp.dup
else
Expand All @@ -208,6 +214,11 @@ def csp_report_only=(new_csp)
end
end
end

if !@csp_report_only.opt_out? && @csp.to_h == ContentSecurityPolicyConfig::DEFAULT
Kernel.warn "#{Kernel.caller.first}: [DEPRECATION] `#csp_report_only=` was configured before `#csp=`. It is assumed you intended to opt out of `#csp=` so be sure to add `config.csp = SecureHeaders::OPT_OUT` to your config. Ensure that #csp_report_only is configured after #csp="
@csp = OPT_OUT
end
end

protected
Expand Down
8 changes: 8 additions & 0 deletions lib/secure_headers/headers/content_security_policy_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ def self.attrs
def report_only?
false
end

def make_report_only
ContentSecurityPolicyReportOnlyConfig.new(self.to_h)
end
end

class ContentSecurityPolicyReportOnlyConfig < ContentSecurityPolicyConfig
Expand All @@ -116,5 +120,9 @@ class ContentSecurityPolicyReportOnlyConfig < ContentSecurityPolicyConfig
def report_only?
true
end

def make_report_only
self
end
end
end
46 changes: 44 additions & 2 deletions spec/lib/secure_headers_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ module SecureHeaders
describe "#header_hash_for" do
it "allows you to opt out of individual headers via API" do
Configuration.default do |config|
config.csp_report_only = { default_src: %w('self')} # no default value
config.csp = { default_src: %w('self')}
config.csp_report_only = config.csp
end
SecureHeaders.opt_out_of_header(request, ContentSecurityPolicyConfig::CONFIG_KEY)
SecureHeaders.opt_out_of_header(request, ContentSecurityPolicyReportOnlyConfig::CONFIG_KEY)
Expand Down Expand Up @@ -61,7 +62,8 @@ module SecureHeaders
it "allows you to opt out entirely" do
# configure the disabled-by-default headers to ensure they also do not get set
Configuration.default do |config|
config.csp_report_only = { :default_src => ["example.com"] }
config.csp = { :default_src => ["example.com"] }
config.csp_report_only = config.csp
config.hpkp = {
report_only: false,
max_age: 10000000,
Expand Down Expand Up @@ -293,6 +295,46 @@ module SecureHeaders
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; script-src 'self'")
end

it "allows you to opt-out of enforced CSP" do
Configuration.default do |config|
config.csp = SecureHeaders::OPT_OUT
config.csp_report_only = {
default_src: %w('self')
}
end

hash = SecureHeaders.header_hash_for(request)
expect(hash['Content-Security-Policy']).to be_nil
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'")
end

it "opts-out of enforced CSP when only csp_report_only is set" do
expect(Kernel).to receive(:warn).once
Configuration.default do |config|
config.csp_report_only = {
default_src: %w('self')
}
end

hash = SecureHeaders.header_hash_for(request)
expect(hash['Content-Security-Policy']).to be_nil
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'")
end

it "allows you to set csp_report_only before csp" do
expect(Kernel).to receive(:warn).once
Configuration.default do |config|
config.csp_report_only = {
default_src: %w('self')
}
config.csp = config.csp_report_only.merge({script_src: %w('unsafe-inline')})
end

hash = SecureHeaders.header_hash_for(request)
expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src 'self' 'unsafe-inline'")
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'")
end

it "allows appending to the enforced policy" do
SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :enforced)
hash = SecureHeaders.header_hash_for(request)
Expand Down

0 comments on commit 3225ed6

Please sign in to comment.