secure_headers 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +9 -0
- data/.travis.yml +3 -2
- data/Gemfile +4 -0
- data/Guardfile +1 -1
- data/HISTORY.md +14 -0
- data/README.md +2 -2
- data/Rakefile +41 -1
- data/app/controllers/content_security_policy_controller.rb +19 -10
- data/fixtures/rails_3_2_12/.rspec +1 -0
- data/fixtures/rails_3_2_12/Gemfile +14 -0
- data/fixtures/rails_3_2_12/Guardfile +14 -0
- data/fixtures/rails_3_2_12/README.rdoc +261 -0
- data/fixtures/rails_3_2_12/Rakefile +7 -0
- data/fixtures/rails_3_2_12/app/controllers/application_controller.rb +4 -0
- data/fixtures/rails_3_2_12/app/controllers/other_things_controller.rb +5 -0
- data/fixtures/rails_3_2_12/app/controllers/things_controller.rb +6 -0
- data/fixtures/rails_3_2_12/app/models/.gitkeep +0 -0
- data/fixtures/rails_3_2_12/app/models/thing.rb +3 -0
- data/fixtures/rails_3_2_12/app/views/layouts/application.html.erb +14 -0
- data/fixtures/rails_3_2_12/app/views/other_things/index.html.erb +1 -0
- data/fixtures/rails_3_2_12/app/views/things/index.html.erb +21 -0
- data/fixtures/rails_3_2_12/config.ru +4 -0
- data/fixtures/rails_3_2_12/config/application.rb +68 -0
- data/fixtures/rails_3_2_12/config/boot.rb +6 -0
- data/fixtures/rails_3_2_12/config/database.yml +25 -0
- data/fixtures/rails_3_2_12/config/environment.rb +5 -0
- data/fixtures/rails_3_2_12/config/environments/development.rb +37 -0
- data/fixtures/rails_3_2_12/config/environments/production.rb +67 -0
- data/fixtures/rails_3_2_12/config/environments/test.rb +37 -0
- data/fixtures/rails_3_2_12/config/initializers/backtrace_silencers.rb +7 -0
- data/fixtures/rails_3_2_12/config/initializers/inflections.rb +15 -0
- data/fixtures/rails_3_2_12/config/initializers/mime_types.rb +5 -0
- data/fixtures/rails_3_2_12/config/initializers/secret_token.rb +7 -0
- data/fixtures/rails_3_2_12/config/initializers/secure_headers.rb +15 -0
- data/fixtures/rails_3_2_12/config/initializers/session_store.rb +8 -0
- data/fixtures/rails_3_2_12/config/initializers/wrap_parameters.rb +14 -0
- data/fixtures/rails_3_2_12/config/locales/en.yml +5 -0
- data/fixtures/rails_3_2_12/config/routes.rb +61 -0
- data/fixtures/rails_3_2_12/db/schema.rb +16 -0
- data/fixtures/rails_3_2_12/db/seeds.rb +7 -0
- data/fixtures/rails_3_2_12/lib/assets/.gitkeep +0 -0
- data/fixtures/rails_3_2_12/lib/tasks/.gitkeep +0 -0
- data/fixtures/rails_3_2_12/log/.gitkeep +0 -0
- data/fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb +40 -0
- data/fixtures/rails_3_2_12/spec/controllers/things_controller_spec.rb +47 -0
- data/fixtures/rails_3_2_12/spec/spec_helper.rb +19 -0
- data/fixtures/rails_3_2_12/vendor/assets/javascripts/.gitkeep +0 -0
- data/fixtures/rails_3_2_12/vendor/assets/stylesheets/.gitkeep +0 -0
- data/fixtures/rails_3_2_12/vendor/plugins/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/.rspec +1 -0
- data/fixtures/rails_3_2_12_no_init/Gemfile +14 -0
- data/fixtures/rails_3_2_12_no_init/Guardfile +14 -0
- data/fixtures/rails_3_2_12_no_init/README.rdoc +261 -0
- data/fixtures/rails_3_2_12_no_init/Rakefile +7 -0
- data/fixtures/rails_3_2_12_no_init/app/controllers/application_controller.rb +4 -0
- data/fixtures/rails_3_2_12_no_init/app/controllers/other_things_controller.rb +7 -0
- data/fixtures/rails_3_2_12_no_init/app/controllers/things_controller.rb +5 -0
- data/fixtures/rails_3_2_12_no_init/app/models/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/app/models/thing.rb +3 -0
- data/fixtures/rails_3_2_12_no_init/app/views/layouts/application.html.erb +14 -0
- data/fixtures/rails_3_2_12_no_init/app/views/other_things/index.html.erb +1 -0
- data/fixtures/rails_3_2_12_no_init/app/views/things/_form.html.erb +17 -0
- data/fixtures/rails_3_2_12_no_init/app/views/things/edit.html.erb +6 -0
- data/fixtures/rails_3_2_12_no_init/app/views/things/index.html.erb +21 -0
- data/fixtures/rails_3_2_12_no_init/app/views/things/new.html.erb +5 -0
- data/fixtures/rails_3_2_12_no_init/app/views/things/show.html.erb +5 -0
- data/fixtures/rails_3_2_12_no_init/config.ru +4 -0
- data/fixtures/rails_3_2_12_no_init/config/application.rb +68 -0
- data/fixtures/rails_3_2_12_no_init/config/boot.rb +6 -0
- data/fixtures/rails_3_2_12_no_init/config/database.yml +25 -0
- data/fixtures/rails_3_2_12_no_init/config/environment.rb +5 -0
- data/fixtures/rails_3_2_12_no_init/config/environments/development.rb +37 -0
- data/fixtures/rails_3_2_12_no_init/config/environments/production.rb +67 -0
- data/fixtures/rails_3_2_12_no_init/config/environments/test.rb +37 -0
- data/fixtures/rails_3_2_12_no_init/config/initializers/backtrace_silencers.rb +7 -0
- data/fixtures/rails_3_2_12_no_init/config/initializers/inflections.rb +15 -0
- data/fixtures/rails_3_2_12_no_init/config/initializers/mime_types.rb +5 -0
- data/fixtures/rails_3_2_12_no_init/config/initializers/secret_token.rb +7 -0
- data/fixtures/rails_3_2_12_no_init/config/initializers/session_store.rb +8 -0
- data/fixtures/rails_3_2_12_no_init/config/initializers/wrap_parameters.rb +14 -0
- data/fixtures/rails_3_2_12_no_init/config/locales/en.yml +5 -0
- data/fixtures/rails_3_2_12_no_init/config/routes.rb +61 -0
- data/fixtures/rails_3_2_12_no_init/db/schema.rb +16 -0
- data/fixtures/rails_3_2_12_no_init/db/seeds.rb +7 -0
- data/fixtures/rails_3_2_12_no_init/lib/assets/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/lib/tasks/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/log/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/spec/controllers/other_things_controller_spec.rb +40 -0
- data/fixtures/rails_3_2_12_no_init/spec/controllers/things_controller_spec.rb +44 -0
- data/fixtures/rails_3_2_12_no_init/spec/spec_helper.rb +20 -0
- data/fixtures/rails_3_2_12_no_init/vendor/assets/javascripts/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/vendor/assets/stylesheets/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/vendor/plugins/.gitkeep +0 -0
- data/lib/secure_headers.rb +19 -15
- data/lib/secure_headers/headers/content_security_policy.rb +54 -113
- data/lib/secure_headers/headers/content_security_policy/browser_strategy.rb +70 -0
- data/lib/secure_headers/headers/content_security_policy/firefox_browser_strategy.rb +72 -0
- data/lib/secure_headers/headers/content_security_policy/ie_browser_strategy.rb +6 -0
- data/lib/secure_headers/headers/content_security_policy/webkit_browser_strategy.rb +9 -0
- data/lib/secure_headers/version.rb +1 -1
- data/{secure-headers.gemspec → secure_headers.gemspec} +0 -0
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +72 -84
- data/travis.sh +10 -0
- metadata +93 -3
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require "forwardable"
|
|
2
|
+
|
|
3
|
+
module SecureHeaders
|
|
4
|
+
class ContentSecurityPolicy
|
|
5
|
+
class BrowserStrategy
|
|
6
|
+
extend Forwardable
|
|
7
|
+
|
|
8
|
+
def_delegators :@content_security_policy, :browser, :experimental, :enforce, :config
|
|
9
|
+
|
|
10
|
+
def self.build(content_security_policy)
|
|
11
|
+
browser = content_security_policy.browser
|
|
12
|
+
klass = if browser.ie?
|
|
13
|
+
IeBrowserStrategy
|
|
14
|
+
elsif browser.firefox?
|
|
15
|
+
FirefoxBrowserStrategy
|
|
16
|
+
else
|
|
17
|
+
WebkitBrowserStrategy
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
klass.new content_security_policy
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize(content_security_policy)
|
|
24
|
+
@content_security_policy = content_security_policy
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def base_name
|
|
28
|
+
SecureHeaders::ContentSecurityPolicy::STANDARD_HEADER_NAME
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def name
|
|
32
|
+
base = base_name
|
|
33
|
+
if !enforce || experimental
|
|
34
|
+
base += "-Report-Only"
|
|
35
|
+
end
|
|
36
|
+
base
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def csp_header
|
|
40
|
+
SecureHeaders::ContentSecurityPolicy::WEBKIT_CSP_HEADER
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def directives
|
|
44
|
+
SecureHeaders::ContentSecurityPolicy::WEBKIT_DIRECTIVES
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def filter_unsupported_directives(config)
|
|
48
|
+
config = config.dup
|
|
49
|
+
config.delete(:frame_ancestors)
|
|
50
|
+
config
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def translate_inline_or_eval val
|
|
54
|
+
val == 'inline' ? "'unsafe-inline'" : "'unsafe-eval'"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def build_impl_specific_directives(default)
|
|
58
|
+
if default.any?
|
|
59
|
+
"default-src #{default.join(" ")}; "
|
|
60
|
+
else
|
|
61
|
+
""
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def normalize_reporting_endpoint?
|
|
66
|
+
false
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module SecureHeaders
|
|
2
|
+
class ContentSecurityPolicy
|
|
3
|
+
class FirefoxBrowserStrategy < BrowserStrategy
|
|
4
|
+
def base_name
|
|
5
|
+
SecureHeaders::ContentSecurityPolicy::FIREFOX_CSP_HEADER_NAME
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def csp_header
|
|
9
|
+
SecureHeaders::ContentSecurityPolicy::FIREFOX_CSP_HEADER
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def directives
|
|
13
|
+
SecureHeaders::ContentSecurityPolicy::FIREFOX_DIRECTIVES
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def filter_unsupported_directives(config)
|
|
17
|
+
config = config.dup
|
|
18
|
+
config[:xhr_src] = config.delete(:connect_src) if config[:connect_src]
|
|
19
|
+
config
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def translate_inline_or_eval val
|
|
23
|
+
val == 'inline' ? 'inline-script' : 'eval-script'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def build_impl_specific_directives(default)
|
|
27
|
+
build_firefox_specific_preamble(default) || ''
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def build_firefox_specific_preamble(default_src_value)
|
|
31
|
+
header_value = ''
|
|
32
|
+
header_value += "allow #{default_src_value.join(" ")}; " if default_src_value.any?
|
|
33
|
+
|
|
34
|
+
options_directive = build_options_directive
|
|
35
|
+
header_value += "options #{options_directive.join(" ")}; " if options_directive.any?
|
|
36
|
+
header_value
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# moves inline/eval values from script-src to options
|
|
40
|
+
# discards those values in the style-src directive
|
|
41
|
+
def build_options_directive
|
|
42
|
+
options_directive = []
|
|
43
|
+
config.each do |directive, val|
|
|
44
|
+
next if val.is_a?(String)
|
|
45
|
+
new_val = []
|
|
46
|
+
val.each do |token|
|
|
47
|
+
if ['inline-script', 'eval-script'].include?(token)
|
|
48
|
+
# Firefox does not support blocking inline styles ATM
|
|
49
|
+
# https://bugzilla.mozilla.org/show_bug.cgi?id=763879
|
|
50
|
+
unless directive?(directive, "style_src") || options_directive.include?(token)
|
|
51
|
+
options_directive << token
|
|
52
|
+
end
|
|
53
|
+
else
|
|
54
|
+
new_val << token
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
config[directive] = new_val
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
options_directive
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def directive? val, name
|
|
64
|
+
val.to_s.casecmp(name) == 0
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def normalize_reporting_endpoint?
|
|
68
|
+
true
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
File without changes
|
|
@@ -28,28 +28,31 @@ module SecureHeaders
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
describe "#name" do
|
|
31
|
+
context "when supplying options to override request" do
|
|
32
|
+
specify { ContentSecurityPolicy.new(default_opts, :ua => IE).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
|
|
33
|
+
specify { ContentSecurityPolicy.new(default_opts, :ua => FIREFOX).name.should == FIREFOX_CSP_HEADER_NAME + "-Report-Only"}
|
|
34
|
+
specify { ContentSecurityPolicy.new(default_opts, :ua => CHROME).name.should == WEBKIT_CSP_HEADER_NAME + "-Report-Only"}
|
|
35
|
+
end
|
|
36
|
+
|
|
31
37
|
context "when in report-only mode" do
|
|
32
|
-
specify { ContentSecurityPolicy.new(request_for(IE)
|
|
33
|
-
specify { ContentSecurityPolicy.new(request_for(FIREFOX)
|
|
34
|
-
specify { ContentSecurityPolicy.new(request_for(
|
|
35
|
-
specify { ContentSecurityPolicy.new(request_for(CHROME), default_opts).name.should == WEBKIT_CSP_HEADER_NAME + "-Report-Only"}
|
|
38
|
+
specify { ContentSecurityPolicy.new(default_opts, :request => request_for(IE)).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
|
|
39
|
+
specify { ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX)).name.should == FIREFOX_CSP_HEADER_NAME + "-Report-Only"}
|
|
40
|
+
specify { ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME)).name.should == WEBKIT_CSP_HEADER_NAME + "-Report-Only"}
|
|
36
41
|
end
|
|
37
42
|
|
|
38
43
|
context "when in enforce mode" do
|
|
39
44
|
let(:opts) { default_opts.merge(:enforce => true)}
|
|
40
45
|
|
|
41
|
-
specify { ContentSecurityPolicy.new(request_for(IE)
|
|
42
|
-
specify { ContentSecurityPolicy.new(request_for(FIREFOX)
|
|
43
|
-
specify { ContentSecurityPolicy.new(request_for(
|
|
44
|
-
specify { ContentSecurityPolicy.new(request_for(CHROME), opts).name.should == WEBKIT_CSP_HEADER_NAME}
|
|
46
|
+
specify { ContentSecurityPolicy.new(opts, :request => request_for(IE)).name.should == STANDARD_HEADER_NAME}
|
|
47
|
+
specify { ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX)).name.should == FIREFOX_CSP_HEADER_NAME}
|
|
48
|
+
specify { ContentSecurityPolicy.new(opts, :request => request_for(CHROME)).name.should == WEBKIT_CSP_HEADER_NAME}
|
|
45
49
|
end
|
|
46
50
|
|
|
47
51
|
context "when in experimental mode" do
|
|
48
52
|
let(:opts) { default_opts.merge(:enforce => true).merge(:experimental => {})}
|
|
49
|
-
specify { ContentSecurityPolicy.new(
|
|
50
|
-
specify { ContentSecurityPolicy.new(
|
|
51
|
-
specify { ContentSecurityPolicy.new(
|
|
52
|
-
specify { ContentSecurityPolicy.new(request_for(CHROME), opts, :experimental => true).name.should == WEBKIT_CSP_HEADER_NAME + "-Report-Only"}
|
|
53
|
+
specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(IE)}).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
|
|
54
|
+
specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(FIREFOX)}).name.should == FIREFOX_CSP_HEADER_NAME + "-Report-Only"}
|
|
55
|
+
specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(CHROME)}).name.should == WEBKIT_CSP_HEADER_NAME + "-Report-Only"}
|
|
53
56
|
end
|
|
54
57
|
end
|
|
55
58
|
|
|
@@ -63,7 +66,7 @@ module SecureHeaders
|
|
|
63
66
|
|
|
64
67
|
context "X-Content-Security-Policy" do
|
|
65
68
|
it "converts the script values to their equivilents" do
|
|
66
|
-
csp = ContentSecurityPolicy.new(request_for(FIREFOX)
|
|
69
|
+
csp = ContentSecurityPolicy.new(@opts, :request => request_for(FIREFOX))
|
|
67
70
|
csp.value.should include("script-src https://* data: 'self' 'none'")
|
|
68
71
|
csp.value.should include('options inline-script eval-script')
|
|
69
72
|
end
|
|
@@ -71,7 +74,7 @@ module SecureHeaders
|
|
|
71
74
|
|
|
72
75
|
context "X-Webkit-CSP" do
|
|
73
76
|
it "converts the script values to their equivilents" do
|
|
74
|
-
csp = ContentSecurityPolicy.new(request_for(CHROME)
|
|
77
|
+
csp = ContentSecurityPolicy.new(@opts, :request => request_for(CHROME))
|
|
75
78
|
csp.value.should include("script-src 'unsafe-inline' 'unsafe-eval' https://* data: 'self' 'none' chrome-extension")
|
|
76
79
|
end
|
|
77
80
|
end
|
|
@@ -84,7 +87,7 @@ module SecureHeaders
|
|
|
84
87
|
:default_src => 'https://*',
|
|
85
88
|
:script_src => "inline eval https://*"
|
|
86
89
|
}
|
|
87
|
-
csp = ContentSecurityPolicy.new(request_for(FIREFOX)
|
|
90
|
+
csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX))
|
|
88
91
|
browser_specific = csp.send :build_impl_specific_directives
|
|
89
92
|
browser_specific.should include('options inline-script eval-script;')
|
|
90
93
|
end
|
|
@@ -94,7 +97,7 @@ module SecureHeaders
|
|
|
94
97
|
:default_src => 'https://*',
|
|
95
98
|
:style_src => "inline eval https://*"
|
|
96
99
|
}
|
|
97
|
-
csp = ContentSecurityPolicy.new(request_for(FIREFOX)
|
|
100
|
+
csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX))
|
|
98
101
|
browser_specific = csp.send :build_impl_specific_directives
|
|
99
102
|
browser_specific.should_not include('inline-script')
|
|
100
103
|
browser_specific.should_not include('eval-script')
|
|
@@ -102,75 +105,55 @@ module SecureHeaders
|
|
|
102
105
|
end
|
|
103
106
|
end
|
|
104
107
|
|
|
105
|
-
describe "#supports_standard?" do
|
|
106
|
-
['IE', 'Safari', 'Chrome'].each do |browser_name|
|
|
107
|
-
it "returns true for #{browser_name}" do
|
|
108
|
-
browser = Brwsr::Browser.new(:ua => browser_name)
|
|
109
|
-
subject.stub(:browser).and_return(browser)
|
|
110
|
-
subject.send(:supports_standard?).should be_true
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
it "returns true for Firefox v >= 18" do
|
|
115
|
-
browser = Brwsr::Browser.new(:ua => "Firefox 18")
|
|
116
|
-
subject.stub(:browser).and_return(browser)
|
|
117
|
-
subject.send(:supports_standard?).should be_true
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
it "returns false for Firefox v < 18" do
|
|
121
|
-
browser = Brwsr::Browser.new(:ua => "Firefox 17")
|
|
122
|
-
subject.stub(:browser).and_return(browser)
|
|
123
|
-
subject.send(:supports_standard?).should be_false
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
108
|
describe "#same_origin?" do
|
|
128
109
|
let(:origin) {"https://example.com:123"}
|
|
129
110
|
|
|
130
111
|
it "matches when host, scheme, and port match" do
|
|
131
|
-
csp = ContentSecurityPolicy.new(
|
|
112
|
+
csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com'}, :request => request_for(FIREFOX, "https://example.com"))
|
|
132
113
|
csp.send(:same_origin?).should be_true
|
|
133
114
|
|
|
134
|
-
csp = ContentSecurityPolicy.new(
|
|
115
|
+
csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com'}, :request => request_for(FIREFOX, "https://example.com:443"))
|
|
135
116
|
csp.send(:same_origin?).should be_true
|
|
136
117
|
|
|
137
|
-
csp = ContentSecurityPolicy.new(
|
|
118
|
+
csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com:123'}, :request => request_for(FIREFOX, "https://example.com:123"))
|
|
138
119
|
csp.send(:same_origin?).should be_true
|
|
139
120
|
|
|
140
|
-
csp = ContentSecurityPolicy.new(
|
|
121
|
+
csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com'}, :request => request_for(FIREFOX, "http://example.com"))
|
|
141
122
|
csp.send(:same_origin?).should be_true
|
|
142
123
|
|
|
143
|
-
csp = ContentSecurityPolicy.new(
|
|
124
|
+
csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com:80'}, :request => request_for(FIREFOX, "http://example.com"))
|
|
144
125
|
csp.send(:same_origin?).should be_true
|
|
145
126
|
|
|
146
|
-
csp = ContentSecurityPolicy.new(
|
|
127
|
+
csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com'}, :request => request_for(FIREFOX, "http://example.com:80"))
|
|
147
128
|
csp.send(:same_origin?).should be_true
|
|
148
129
|
end
|
|
149
130
|
|
|
150
131
|
it "does not match port mismatches" do
|
|
151
|
-
csp = ContentSecurityPolicy.new(
|
|
132
|
+
csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com'}, :request => request_for(FIREFOX, "http://example.com:81"))
|
|
152
133
|
csp.send(:same_origin?).should be_false
|
|
153
134
|
end
|
|
154
135
|
|
|
155
136
|
it "does not match host mismatches" do
|
|
156
|
-
csp = ContentSecurityPolicy.new(
|
|
137
|
+
csp = ContentSecurityPolicy.new({:report_uri => 'http://twitter.com'}, :request => request_for(FIREFOX, "http://example.com"))
|
|
157
138
|
csp.send(:same_origin?).should be_false
|
|
158
139
|
end
|
|
159
140
|
|
|
160
141
|
it "does not match host mismatches because of subdomains" do
|
|
161
|
-
csp = ContentSecurityPolicy.new(
|
|
142
|
+
csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com'}, :request => request_for(FIREFOX, "http://sub.example.com"))
|
|
162
143
|
csp.send(:same_origin?).should be_false
|
|
163
144
|
end
|
|
164
145
|
|
|
165
146
|
it "does not match scheme mismatches" do
|
|
166
|
-
csp = ContentSecurityPolicy.new(
|
|
147
|
+
csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com'}, :request => request_for(FIREFOX, "ftp://example.com"))
|
|
167
148
|
csp.send(:same_origin?).should be_false
|
|
168
149
|
end
|
|
169
150
|
|
|
170
151
|
it "does not match on substring collisions" do
|
|
171
|
-
csp = ContentSecurityPolicy.new(
|
|
152
|
+
csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com'}, :request => request_for(FIREFOX, "https://anotherexample.com"))
|
|
172
153
|
csp.send(:same_origin?).should be_false
|
|
173
154
|
end
|
|
155
|
+
|
|
156
|
+
|
|
174
157
|
end
|
|
175
158
|
|
|
176
159
|
describe "#normalize_reporting_endpoint" do
|
|
@@ -178,69 +161,69 @@ module SecureHeaders
|
|
|
178
161
|
|
|
179
162
|
context "when using firefox" do
|
|
180
163
|
it "updates the report-uri when posting to a different host" do
|
|
181
|
-
|
|
182
|
-
|
|
164
|
+
csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX, "https://anexample.com"))
|
|
165
|
+
csp.report_uri.should == FF_CSP_ENDPOINT
|
|
183
166
|
end
|
|
184
167
|
|
|
185
|
-
it "
|
|
186
|
-
|
|
187
|
-
|
|
168
|
+
it "doesn't set report-uri if no forward_endpoint is supplied" do
|
|
169
|
+
csp = ContentSecurityPolicy.new({:report_uri => "https://another.example.com"}, :request => request_for(FIREFOX, "https://anexample.com"))
|
|
170
|
+
csp.report_uri.should be_nil
|
|
188
171
|
end
|
|
189
172
|
|
|
190
|
-
it "
|
|
191
|
-
|
|
192
|
-
|
|
173
|
+
it "forwards if the request_uri is set to a non-matching value" do
|
|
174
|
+
csp = ContentSecurityPolicy.new({:report_uri => "https://another.example.com", :forward_endpoint => '/somewhere'}, :ua => "Firefox", :request_uri => "https://anexample.com")
|
|
175
|
+
csp.report_uri.should == FF_CSP_ENDPOINT
|
|
193
176
|
end
|
|
194
177
|
end
|
|
195
178
|
|
|
196
179
|
it "does not update the URI is the report_uri is on the same origin" do
|
|
197
180
|
opts = {:report_uri => 'https://example.com/csp', :forward_endpoint => 'https://anotherexample.com'}
|
|
198
|
-
|
|
199
|
-
|
|
181
|
+
csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX, "https://example.com/somewhere"))
|
|
182
|
+
csp.report_uri.should == 'https://example.com/csp'
|
|
200
183
|
end
|
|
201
184
|
|
|
202
185
|
it "does not update the report-uri when using a non-firefox browser" do
|
|
203
|
-
|
|
204
|
-
|
|
186
|
+
csp = ContentSecurityPolicy.new(opts, :request => request_for(CHROME))
|
|
187
|
+
csp.report_uri.should == 'https://example.com/csp'
|
|
205
188
|
end
|
|
206
189
|
end
|
|
207
190
|
|
|
208
191
|
describe "#value" do
|
|
209
192
|
it "raises an exception when default-src is missing" do
|
|
210
|
-
|
|
193
|
+
csp = ContentSecurityPolicy.new({:script_src => 'anything'}, :request => request_for(CHROME))
|
|
211
194
|
lambda {
|
|
212
|
-
|
|
195
|
+
csp.value
|
|
213
196
|
}.should raise_error(ContentSecurityPolicyBuildError, "Couldn't build CSP header :( Expected to find default_src directive value")
|
|
214
197
|
end
|
|
215
198
|
|
|
216
199
|
context "auto-whitelists data: uris for img-src" do
|
|
217
200
|
|
|
218
201
|
it "sets the value if no img-src specified" do
|
|
219
|
-
csp = ContentSecurityPolicy.new(
|
|
202
|
+
csp = ContentSecurityPolicy.new({:default_src => 'self', :disable_fill_missing => true, :disable_chrome_extension => true}, :request => request_for(CHROME))
|
|
220
203
|
csp.value.should == "default-src 'self'; img-src data:;"
|
|
221
204
|
end
|
|
222
205
|
|
|
223
206
|
it "appends the value if img-src is specified" do
|
|
224
|
-
csp = ContentSecurityPolicy.new(
|
|
207
|
+
csp = ContentSecurityPolicy.new({:default_src => 'self', :img_src => 'self', :disable_fill_missing => true, :disable_chrome_extension => true}, :request => request_for(CHROME))
|
|
225
208
|
csp.value.should == "default-src 'self'; img-src 'self' data:;"
|
|
226
209
|
end
|
|
227
210
|
end
|
|
228
211
|
|
|
229
212
|
it "fills in directives without values with default-src value" do
|
|
230
213
|
options = default_opts.merge(:disable_fill_missing => false)
|
|
231
|
-
csp = ContentSecurityPolicy.new(request_for(CHROME)
|
|
214
|
+
csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
|
|
232
215
|
value = "default-src https://*; connect-src https://*; font-src https://*; frame-src https://*; img-src https://* data:; media-src https://*; object-src https://*; script-src 'unsafe-inline' 'unsafe-eval' https://* data:; style-src 'unsafe-inline' https://* chrome-extension: about:; report-uri /csp_report;"
|
|
233
216
|
csp.value.should == value
|
|
234
217
|
end
|
|
235
218
|
|
|
236
219
|
it "sends the chrome csp header if an unknown browser is supplied" do
|
|
237
|
-
csp = ContentSecurityPolicy.new(request_for(IE)
|
|
220
|
+
csp = ContentSecurityPolicy.new(default_opts, :request => request_for(IE))
|
|
238
221
|
csp.value.should match "default-src"
|
|
239
222
|
end
|
|
240
223
|
|
|
241
224
|
context "X-Content-Security-Policy" do
|
|
242
225
|
it "builds a csp header for firefox" do
|
|
243
|
-
csp = ContentSecurityPolicy.new(request_for(FIREFOX)
|
|
226
|
+
csp = ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX))
|
|
244
227
|
csp.value.should == "allow https://*; options inline-script eval-script; img-src data:; script-src https://* data:; style-src https://* chrome-extension: about:;"
|
|
245
228
|
end
|
|
246
229
|
|
|
@@ -251,7 +234,7 @@ module SecureHeaders
|
|
|
251
234
|
:disable_chrome_extension => true,
|
|
252
235
|
:disable_fill_missing => true
|
|
253
236
|
}
|
|
254
|
-
csp = ContentSecurityPolicy.new(request_for(FIREFOX)
|
|
237
|
+
csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX))
|
|
255
238
|
csp.value.should =~ /xhr-src 'self' http:/
|
|
256
239
|
end
|
|
257
240
|
|
|
@@ -262,24 +245,19 @@ module SecureHeaders
|
|
|
262
245
|
:disable_chrome_extension => true,
|
|
263
246
|
:disable_fill_missing => true
|
|
264
247
|
}
|
|
265
|
-
csp = ContentSecurityPolicy.new(request_for(FIREFOX_18)
|
|
248
|
+
csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX_18))
|
|
266
249
|
csp.value.should =~ /xhr-src 'self' http:\/\/\*\.localhost\.com:\*/
|
|
267
250
|
end
|
|
268
|
-
|
|
269
|
-
it "builds a w3c-style-ish header for Firefox > version 18" do
|
|
270
|
-
csp = ContentSecurityPolicy.new(request_for(FIREFOX_18), default_opts)
|
|
271
|
-
csp.value.should =~ /default-src/
|
|
272
|
-
end
|
|
273
251
|
end
|
|
274
252
|
|
|
275
253
|
context "X-Webkit-CSP" do
|
|
276
254
|
it "builds a csp header for chrome" do
|
|
277
|
-
csp = ContentSecurityPolicy.new(request_for(CHROME)
|
|
255
|
+
csp = ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME))
|
|
278
256
|
csp.value.should == "default-src https://*; img-src data:; script-src 'unsafe-inline' 'unsafe-eval' https://* data:; style-src 'unsafe-inline' https://* chrome-extension: about:; report-uri /csp_report;"
|
|
279
257
|
end
|
|
280
258
|
|
|
281
259
|
it "ignores :forward_endpoint settings" do
|
|
282
|
-
csp = ContentSecurityPolicy.new(request_for(CHROME)
|
|
260
|
+
csp = ContentSecurityPolicy.new(@options_with_forwarding, :request => request_for(CHROME))
|
|
283
261
|
csp.value.should =~ /report-uri #{@options_with_forwarding[:report_uri]};/
|
|
284
262
|
end
|
|
285
263
|
|
|
@@ -291,7 +269,7 @@ module SecureHeaders
|
|
|
291
269
|
:style_src => "inline https://* chrome-extension: about:"
|
|
292
270
|
}
|
|
293
271
|
|
|
294
|
-
csp = ContentSecurityPolicy.new(request_for(CHROME)
|
|
272
|
+
csp = ContentSecurityPolicy.new(opts, :request => request_for(CHROME))
|
|
295
273
|
|
|
296
274
|
# ignore the report-uri directive
|
|
297
275
|
csp.value.split(';')[0...-1].each{|directive| directive.should =~ /chrome-extension:/}
|
|
@@ -311,12 +289,12 @@ module SecureHeaders
|
|
|
311
289
|
|
|
312
290
|
let(:header) {}
|
|
313
291
|
it "returns the original value" do
|
|
314
|
-
header = ContentSecurityPolicy.new(request_for(CHROME)
|
|
292
|
+
header = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
|
|
315
293
|
header.value.should == "default-src 'self'; img-src data:; script-src https://*;"
|
|
316
294
|
end
|
|
317
295
|
|
|
318
296
|
it "it returns the experimental value if requested" do
|
|
319
|
-
header = ContentSecurityPolicy.new(request_for(CHROME),
|
|
297
|
+
header = ContentSecurityPolicy.new(options, {:request => request_for(CHROME), :experimental => true})
|
|
320
298
|
header.value.should_not =~ /https/
|
|
321
299
|
end
|
|
322
300
|
end
|
|
@@ -332,12 +310,17 @@ module SecureHeaders
|
|
|
332
310
|
}
|
|
333
311
|
|
|
334
312
|
it "adds directive values for headers on http" do
|
|
335
|
-
csp = ContentSecurityPolicy.new(request_for(CHROME)
|
|
313
|
+
csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
|
|
336
314
|
csp.value.should == "default-src https://*; frame-src http://*; img-src http://* data:; script-src 'unsafe-inline' 'unsafe-eval' https://* data:; style-src 'unsafe-inline' https://* chrome-extension: about:; report-uri /csp_report;"
|
|
337
315
|
end
|
|
338
316
|
|
|
339
317
|
it "does not add the directive values if requesting https" do
|
|
340
|
-
csp = ContentSecurityPolicy.new(request_for(CHROME, '/', :ssl => true)
|
|
318
|
+
csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME, '/', :ssl => true))
|
|
319
|
+
csp.value.should_not =~ /http:/
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
it "does not add the directive values if requesting https" do
|
|
323
|
+
csp = ContentSecurityPolicy.new(options, :ua => "Chrome", :ssl => true)
|
|
341
324
|
csp.value.should_not =~ /http:/
|
|
342
325
|
end
|
|
343
326
|
|
|
@@ -367,12 +350,17 @@ module SecureHeaders
|
|
|
367
350
|
# "allow 'self; script-src https://* http://*" for http requests
|
|
368
351
|
|
|
369
352
|
it "uses the value in the experimental block over SSL" do
|
|
370
|
-
csp = ContentSecurityPolicy.new(
|
|
353
|
+
csp = ContentSecurityPolicy.new(options, :experimental => true, :request => request_for(FIREFOX, '/', :ssl => true))
|
|
354
|
+
csp.value.should == "allow 'self'; img-src data:; script-src 'self';"
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
it "detects the :ssl => true option" do
|
|
358
|
+
csp = ContentSecurityPolicy.new(options, :experimental => true, :ua => FIREFOX, :ssl => true)
|
|
371
359
|
csp.value.should == "allow 'self'; img-src data:; script-src 'self';"
|
|
372
360
|
end
|
|
373
361
|
|
|
374
362
|
it "merges the values from experimental/http_additions when not over SSL" do
|
|
375
|
-
csp = ContentSecurityPolicy.new(
|
|
363
|
+
csp = ContentSecurityPolicy.new(options, :experimental => true, :request => request_for(FIREFOX))
|
|
376
364
|
csp.value.should == "allow 'self'; img-src data:; script-src 'self' https://mycdn.example.com;"
|
|
377
365
|
end
|
|
378
366
|
end
|