diff --git a/fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb b/fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb index ebbf0f40..99a5b488 100644 --- a/fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb +++ b/fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb @@ -13,7 +13,7 @@ def request(opts = {}) options = opts.merge( { 'HTTPS' => 'on', - 'HTTP_USER_AGENT' => "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0" + 'HTTP_USER_AGENT' => "Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/537.22 (KHTML like Gecko) Chrome/25.0.1364.99 Safari/537.22" } ) diff --git a/fixtures/rails_4_1_8/spec/controllers/other_things_controller_spec.rb b/fixtures/rails_4_1_8/spec/controllers/other_things_controller_spec.rb index 10bcc8cd..6b602565 100644 --- a/fixtures/rails_4_1_8/spec/controllers/other_things_controller_spec.rb +++ b/fixtures/rails_4_1_8/spec/controllers/other_things_controller_spec.rb @@ -13,7 +13,7 @@ def request(opts = {}) options = opts.merge( { 'HTTPS' => 'on', - 'HTTP_USER_AGENT' => "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0" + 'HTTP_USER_AGENT' => "Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/537.22 (KHTML like Gecko) Chrome/25.0.1364.99 Safari/537.22" } ) diff --git a/lib/secure_headers/headers/content_security_policy.rb b/lib/secure_headers/headers/content_security_policy.rb index 88214b15..407c663b 100644 --- a/lib/secure_headers/headers/content_security_policy.rb +++ b/lib/secure_headers/headers/content_security_policy.rb @@ -1,6 +1,7 @@ require 'uri' require 'base64' require 'securerandom' +require 'user_agent_parser' module SecureHeaders class ContentSecurityPolicyBuildError < StandardError; end @@ -205,8 +206,12 @@ def translate_dir_value val elsif %{self none}.include?(val) "'#{val}'" elsif val == 'nonce' - self.class.set_nonce(@controller, nonce) - ["'nonce-#{nonce}'", "'unsafe-inline'"] + if supports_nonces?(@ua) + self.class.set_nonce(@controller, nonce) + ["'nonce-#{nonce}'", "'unsafe-inline'"] + else + "'unsafe-inline'" + end else val end @@ -258,5 +263,10 @@ def non_default_directives def build_directive(key) "#{self.class.symbol_to_hyphen_case(key)} #{@config[key].join(" ")}; " end + + def supports_nonces?(user_agent) + parsed_ua = UserAgentParser.parse(user_agent) + ["Chrome", "Opera", "Firefox"].include?(parsed_ua.family) + end end end diff --git a/secure_headers.gemspec b/secure_headers.gemspec index a6a28afe..5353828f 100644 --- a/secure_headers.gemspec +++ b/secure_headers.gemspec @@ -19,5 +19,6 @@ Gem::Specification.new do |gem| gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) gem.require_paths = ["lib"] gem.add_development_dependency "rake" + gem.add_dependency "user_agent_parser" gem.post_install_message = "Warning: lambda config values will be broken until you add |controller|. e.g. :enforce => lambda { |controller| some_expression }" end diff --git a/spec/lib/secure_headers/headers/content_security_policy_spec.rb b/spec/lib/secure_headers/headers/content_security_policy_spec.rb index 7ca0df6c..53a447c1 100644 --- a/spec/lib/secure_headers/headers/content_security_policy_spec.rb +++ b/spec/lib/secure_headers/headers/content_security_policy_spec.rb @@ -17,7 +17,8 @@ module SecureHeaders FIREFOX_23 = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:23.0) Gecko/20131011 Firefox/23.0" CHROME = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4" CHROME_25 = "Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/537.22 (KHTML like Gecko) Chrome/25.0.1364.99 Safari/537.22" - + SAFARI = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A" + OPERA = "Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16" def request_for user_agent, request_uri=nil, options={:ssl => false} double(:ssl? => options[:ssl], :env => {'HTTP_USER_AGENT' => user_agent}, :url => (request_uri || 'http://areallylongdomainexample.com') ) @@ -184,11 +185,33 @@ def request_for user_agent, request_uri=nil, options={:ssl => false} end context "when using a nonce" do - it "adds a nonce and unsafe-inline to the script-src value" do + it "adds a nonce and unsafe-inline to the script-src value when using chrome" do header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce"), :request => request_for(CHROME)) expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'") end + it "adds a nonce and unsafe-inline to the script-src value when using firefox" do + header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce"), :request => request_for(FIREFOX)) + expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'") + end + + it "adds a nonce and unsafe-inline to the script-src value when using opera" do + header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce"), :request => request_for(OPERA)) + expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'") + end + + it "does not add a nonce and unsafe-inline to the script-src value when using Safari" do + header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce"), :request => request_for(SAFARI)) + expect(header.value).to include("script-src 'self' 'unsafe-inline'") + expect(header.value).not_to include("nonce") + end + + it "does not add a nonce and unsafe-inline to the script-src value when using IE" do + header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce"), :request => request_for(IE)) + expect(header.value).to include("script-src 'self' 'unsafe-inline'") + expect(header.value).not_to include("nonce") + end + it "adds a nonce and unsafe-inline to the style-src value" do header = ContentSecurityPolicy.new(default_opts.merge(:style_src => "self nonce"), :request => request_for(CHROME)) expect(header.value).to include("style-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")