Skip to content

Commit eeb09da

Browse files
committed
Merge pull request #220 from twitter/upgrade-insecure-requests
Add support for CSP upgrade-insecure-requests source expression
2 parents 2954163 + f251b33 commit eeb09da

File tree

3 files changed

+46
-29
lines changed

3 files changed

+46
-29
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ SecureHeaders::Configuration.default do |config|
5656
frame_ancestors: %w('none'),
5757
plugin_types: %w(application/x-shockwave-flash),
5858
block_all_mixed_content: true, # see [http://www.w3.org/TR/mixed-content/](http://www.w3.org/TR/mixed-content/)
59+
upgrade_insecure_requests: true, # see https://www.w3.org/TR/upgrade-insecure-requests/
5960
report_uri: %w(https://example.com/uri-directive)
6061
}
6162
config.hpkp = {

lib/secure_headers/headers/content_security_policy.rb

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,10 @@ class ContentSecurityPolicy
7777
# All the directives that are not currently in a formal spec, but have
7878
# been implemented somewhere.
7979
BLOCK_ALL_MIXED_CONTENT = :block_all_mixed_content
80+
UPGRADE_INSECURE_REQUESTS = :upgrade_insecure_requests
8081
DIRECTIVES_DRAFT = [
81-
BLOCK_ALL_MIXED_CONTENT
82+
BLOCK_ALL_MIXED_CONTENT,
83+
UPGRADE_INSECURE_REQUESTS
8284
].freeze
8385

8486
SAFARI_DIRECTIVES = DIRECTIVES_1_0
@@ -90,7 +92,7 @@ class ContentSecurityPolicy
9092
].freeze
9193

9294
FIREFOX_DIRECTIVES = (
93-
DIRECTIVES_2_0 - FIREFOX_UNSUPPORTED_DIRECTIVES
95+
DIRECTIVES_2_0 + DIRECTIVES_DRAFT - FIREFOX_UNSUPPORTED_DIRECTIVES
9496
).freeze
9597

9698
CHROME_DIRECTIVES = (
@@ -114,25 +116,26 @@ class ContentSecurityPolicy
114116
OTHER = "Other".freeze
115117

116118
DIRECTIVE_VALUE_TYPES = {
117-
BASE_URI => :source_list,
118-
BLOCK_ALL_MIXED_CONTENT => :boolean,
119-
CHILD_SRC => :source_list,
120-
CONNECT_SRC => :source_list,
121-
DEFAULT_SRC => :source_list,
122-
FONT_SRC => :source_list,
123-
FORM_ACTION => :source_list,
124-
FRAME_ANCESTORS => :source_list,
125-
FRAME_SRC => :source_list,
126-
IMG_SRC => :source_list,
127-
MANIFEST_SRC => :source_list,
128-
MEDIA_SRC => :source_list,
129-
OBJECT_SRC => :source_list,
130-
PLUGIN_TYPES => :source_list,
131-
REFLECTED_XSS => :string,
132-
REPORT_URI => :source_list,
133-
SANDBOX => :string,
134-
SCRIPT_SRC => :source_list,
135-
STYLE_SRC => :source_list
119+
BASE_URI => :source_list,
120+
BLOCK_ALL_MIXED_CONTENT => :boolean,
121+
CHILD_SRC => :source_list,
122+
CONNECT_SRC => :source_list,
123+
DEFAULT_SRC => :source_list,
124+
FONT_SRC => :source_list,
125+
FORM_ACTION => :source_list,
126+
FRAME_ANCESTORS => :source_list,
127+
FRAME_SRC => :source_list,
128+
IMG_SRC => :source_list,
129+
MANIFEST_SRC => :source_list,
130+
MEDIA_SRC => :source_list,
131+
OBJECT_SRC => :source_list,
132+
PLUGIN_TYPES => :source_list,
133+
REFLECTED_XSS => :string,
134+
REPORT_URI => :source_list,
135+
SANDBOX => :string,
136+
SCRIPT_SRC => :source_list,
137+
STYLE_SRC => :source_list,
138+
UPGRADE_INSECURE_REQUESTS => :boolean
136139
}.freeze
137140

138141
CONFIG_KEY = :csp
@@ -196,7 +199,7 @@ def idempotent_additions?(config, additions)
196199
#
197200
# raises an error if the original config is OPT_OUT
198201
#
199-
# 1. for non-source-list values (report_only, block_all_mixed_content),
202+
# 1. for non-source-list values (report_only, block_all_mixed_content, upgrade_insecure_requests),
200203
# additions will overwrite the original value.
201204
# 2. if a value in additions does not exist in the original config, the
202205
# default-src value is included to match original behavior.

spec/lib/secure_headers/headers/content_security_policy_spec.rb

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ module SecureHeaders
4646
frame_ancestors: %w('none'),
4747
plugin_types: %w(application/x-shockwave-flash),
4848
block_all_mixed_content: true, # see [http://www.w3.org/TR/mixed-content/](http://www.w3.org/TR/mixed-content/)
49+
upgrade_insecure_requests: true, # see https://www.w3.org/TR/upgrade-insecure-requests/
4950
report_uri: %w(https://example.com/uri-directive)
5051
}
5152

@@ -76,6 +77,12 @@ module SecureHeaders
7677
end.to raise_error(ContentSecurityPolicyConfigError)
7778
end
7879

80+
it "requires :upgrade_insecure_requests to be a boolean value" do
81+
expect do
82+
CSP.validate_config!(default_opts.merge(upgrade_insecure_requests: "steve"))
83+
end.to raise_error(ContentSecurityPolicyConfigError)
84+
end
85+
7986
it "requires all source lists to be an array of strings" do
8087
expect do
8188
CSP.validate_config!(default_src: "steve")
@@ -214,27 +221,33 @@ module SecureHeaders
214221

215222
context "browser sniffing" do
216223
let (:complex_opts) do
217-
ContentSecurityPolicy::ALL_DIRECTIVES.each_with_object({}) { |directive, hash| hash[directive] = %w('self') }
218-
.merge(block_all_mixed_content: true, reflected_xss: "block")
219-
.merge(script_src: %w('self'), script_nonce: 123456)
224+
ContentSecurityPolicy::ALL_DIRECTIVES.each_with_object({}) do |directive, hash|
225+
hash[directive] = %w('self')
226+
end.merge({
227+
block_all_mixed_content: true,
228+
upgrade_insecure_requests: true,
229+
reflected_xss: "block",
230+
script_src: %w('self'),
231+
script_nonce: 123456
232+
})
220233
end
221234

222235
it "does not filter any directives for Chrome" do
223236
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:chrome])
224-
expect(policy.value).to eq("default-src 'self'; base-uri 'self'; block-all-mixed-content; child-src 'self'; connect-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'self'; frame-src 'self'; img-src 'self'; media-src 'self'; object-src 'self'; plugin-types 'self'; sandbox 'self'; script-src 'self' 'nonce-123456'; style-src 'self'; report-uri 'self'")
237+
expect(policy.value).to eq("default-src 'self'; base-uri 'self'; block-all-mixed-content; child-src 'self'; connect-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'self'; frame-src 'self'; img-src 'self'; media-src 'self'; object-src 'self'; plugin-types 'self'; sandbox 'self'; script-src 'self' 'nonce-123456'; style-src 'self'; upgrade-insecure-requests; report-uri 'self'")
225238
end
226239

227240
it "does not filter any directives for Opera" do
228241
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:opera])
229-
expect(policy.value).to eq("default-src 'self'; base-uri 'self'; block-all-mixed-content; child-src 'self'; connect-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'self'; frame-src 'self'; img-src 'self'; media-src 'self'; object-src 'self'; plugin-types 'self'; sandbox 'self'; script-src 'self' 'nonce-123456'; style-src 'self'; report-uri 'self'")
242+
expect(policy.value).to eq("default-src 'self'; base-uri 'self'; block-all-mixed-content; child-src 'self'; connect-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'self'; frame-src 'self'; img-src 'self'; media-src 'self'; object-src 'self'; plugin-types 'self'; sandbox 'self'; script-src 'self' 'nonce-123456'; style-src 'self'; upgrade-insecure-requests; report-uri 'self'")
230243
end
231244

232245
it "filters blocked-all-mixed-content, child-src, and plugin-types for firefox" do
233246
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:firefox])
234-
expect(policy.value).to eq("default-src 'self'; base-uri 'self'; connect-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'self'; frame-src 'self'; img-src 'self'; media-src 'self'; object-src 'self'; sandbox 'self'; script-src 'self' 'nonce-123456'; style-src 'self'; report-uri 'self'")
247+
expect(policy.value).to eq("default-src 'self'; base-uri 'self'; connect-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'self'; frame-src 'self'; img-src 'self'; media-src 'self'; object-src 'self'; sandbox 'self'; script-src 'self' 'nonce-123456'; style-src 'self'; upgrade-insecure-requests; report-uri 'self'")
235248
end
236249

237-
it "adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, child-src, form-action, frame-ancestors, nonce sources, hash sources, and plugin-types for safari" do
250+
it "adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, upgrade-insecure-requests, child-src, form-action, frame-ancestors, nonce sources, hash sources, and plugin-types for safari" do
238251
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:safari6])
239252
expect(policy.value).to eq("default-src 'self'; connect-src 'self'; font-src 'self'; frame-src 'self'; img-src 'self'; media-src 'self'; object-src 'self'; sandbox 'self'; script-src 'self' 'unsafe-inline'; style-src 'self'; report-uri 'self'")
240253
end

0 commit comments

Comments
 (0)