secure_headers 2.5.3 → 3.0.0.pre
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.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.travis.yml +2 -1
- data/Gemfile +9 -16
- data/README.md +154 -331
- data/Rakefile +2 -36
- data/lib/secure_headers/configuration.rb +189 -0
- data/lib/secure_headers/headers/content_security_policy.rb +341 -254
- data/lib/secure_headers/headers/public_key_pins.rb +43 -58
- data/lib/secure_headers/headers/strict_transport_security.rb +21 -49
- data/lib/secure_headers/headers/x_content_type_options.rb +18 -33
- data/lib/secure_headers/headers/x_download_options.rb +18 -33
- data/lib/secure_headers/headers/x_frame_options.rb +24 -34
- data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +19 -34
- data/lib/secure_headers/headers/x_xss_protection.rb +17 -48
- data/lib/secure_headers/middleware.rb +15 -0
- data/lib/secure_headers/padrino.rb +1 -2
- data/lib/secure_headers/railtie.rb +9 -6
- data/lib/secure_headers/view_helper.rb +27 -43
- data/lib/secure_headers.rb +254 -61
- data/secure_headers.gemspec +7 -12
- data/spec/lib/secure_headers/configuration_spec.rb +80 -0
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +111 -276
- data/spec/lib/secure_headers/headers/public_key_pins_spec.rb +17 -17
- data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +11 -43
- data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +11 -18
- data/spec/lib/secure_headers/headers/x_download_options_spec.rb +13 -17
- data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +15 -17
- data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +22 -39
- data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +20 -30
- data/spec/lib/secure_headers/middleware_spec.rb +40 -0
- data/spec/lib/secure_headers_spec.rb +201 -339
- data/spec/spec_helper.rb +30 -30
- data/upgrading-to-3-0.md +35 -0
- metadata +14 -100
- data/fixtures/rails_3_2_22/.rspec +0 -1
- data/fixtures/rails_3_2_22/Gemfile +0 -6
- data/fixtures/rails_3_2_22/README.rdoc +0 -261
- data/fixtures/rails_3_2_22/Rakefile +0 -7
- data/fixtures/rails_3_2_22/app/controllers/application_controller.rb +0 -4
- data/fixtures/rails_3_2_22/app/controllers/other_things_controller.rb +0 -5
- data/fixtures/rails_3_2_22/app/controllers/things_controller.rb +0 -5
- data/fixtures/rails_3_2_22/app/models/.gitkeep +0 -0
- data/fixtures/rails_3_2_22/app/views/layouts/application.html.erb +0 -11
- data/fixtures/rails_3_2_22/app/views/other_things/index.html.erb +0 -2
- data/fixtures/rails_3_2_22/app/views/things/index.html.erb +0 -1
- data/fixtures/rails_3_2_22/config/application.rb +0 -14
- data/fixtures/rails_3_2_22/config/boot.rb +0 -6
- data/fixtures/rails_3_2_22/config/environment.rb +0 -5
- data/fixtures/rails_3_2_22/config/environments/test.rb +0 -37
- data/fixtures/rails_3_2_22/config/initializers/secure_headers.rb +0 -16
- data/fixtures/rails_3_2_22/config/routes.rb +0 -4
- data/fixtures/rails_3_2_22/config/script_hashes.yml +0 -5
- data/fixtures/rails_3_2_22/config.ru +0 -7
- data/fixtures/rails_3_2_22/lib/assets/.gitkeep +0 -0
- data/fixtures/rails_3_2_22/lib/tasks/.gitkeep +0 -0
- data/fixtures/rails_3_2_22/log/.gitkeep +0 -0
- data/fixtures/rails_3_2_22/spec/controllers/other_things_controller_spec.rb +0 -83
- data/fixtures/rails_3_2_22/spec/controllers/things_controller_spec.rb +0 -54
- data/fixtures/rails_3_2_22/spec/spec_helper.rb +0 -15
- data/fixtures/rails_3_2_22/vendor/assets/javascripts/.gitkeep +0 -0
- data/fixtures/rails_3_2_22/vendor/assets/stylesheets/.gitkeep +0 -0
- data/fixtures/rails_3_2_22/vendor/plugins/.gitkeep +0 -0
- data/fixtures/rails_3_2_22_no_init/.rspec +0 -1
- data/fixtures/rails_3_2_22_no_init/Gemfile +0 -6
- data/fixtures/rails_3_2_22_no_init/README.rdoc +0 -261
- data/fixtures/rails_3_2_22_no_init/Rakefile +0 -7
- data/fixtures/rails_3_2_22_no_init/app/controllers/application_controller.rb +0 -4
- data/fixtures/rails_3_2_22_no_init/app/controllers/other_things_controller.rb +0 -20
- data/fixtures/rails_3_2_22_no_init/app/controllers/things_controller.rb +0 -5
- data/fixtures/rails_3_2_22_no_init/app/models/.gitkeep +0 -0
- data/fixtures/rails_3_2_22_no_init/app/views/layouts/application.html.erb +0 -12
- data/fixtures/rails_3_2_22_no_init/app/views/other_things/index.html.erb +0 -1
- data/fixtures/rails_3_2_22_no_init/app/views/things/index.html.erb +0 -0
- data/fixtures/rails_3_2_22_no_init/config/application.rb +0 -17
- data/fixtures/rails_3_2_22_no_init/config/boot.rb +0 -6
- data/fixtures/rails_3_2_22_no_init/config/environment.rb +0 -5
- data/fixtures/rails_3_2_22_no_init/config/environments/test.rb +0 -37
- data/fixtures/rails_3_2_22_no_init/config/routes.rb +0 -4
- data/fixtures/rails_3_2_22_no_init/config.ru +0 -4
- data/fixtures/rails_3_2_22_no_init/lib/assets/.gitkeep +0 -0
- data/fixtures/rails_3_2_22_no_init/lib/tasks/.gitkeep +0 -0
- data/fixtures/rails_3_2_22_no_init/log/.gitkeep +0 -0
- data/fixtures/rails_3_2_22_no_init/spec/controllers/other_things_controller_spec.rb +0 -56
- data/fixtures/rails_3_2_22_no_init/spec/controllers/things_controller_spec.rb +0 -54
- data/fixtures/rails_3_2_22_no_init/spec/spec_helper.rb +0 -5
- data/fixtures/rails_3_2_22_no_init/vendor/assets/javascripts/.gitkeep +0 -0
- data/fixtures/rails_3_2_22_no_init/vendor/assets/stylesheets/.gitkeep +0 -0
- data/fixtures/rails_3_2_22_no_init/vendor/plugins/.gitkeep +0 -0
- data/fixtures/rails_4_1_8/Gemfile +0 -5
- data/fixtures/rails_4_1_8/README.rdoc +0 -28
- data/fixtures/rails_4_1_8/Rakefile +0 -6
- data/fixtures/rails_4_1_8/app/controllers/application_controller.rb +0 -4
- data/fixtures/rails_4_1_8/app/controllers/concerns/.keep +0 -0
- data/fixtures/rails_4_1_8/app/controllers/other_things_controller.rb +0 -5
- data/fixtures/rails_4_1_8/app/controllers/things_controller.rb +0 -5
- data/fixtures/rails_4_1_8/app/models/.keep +0 -0
- data/fixtures/rails_4_1_8/app/models/concerns/.keep +0 -0
- data/fixtures/rails_4_1_8/app/views/layouts/application.html.erb +0 -11
- data/fixtures/rails_4_1_8/app/views/other_things/index.html.erb +0 -2
- data/fixtures/rails_4_1_8/app/views/things/index.html.erb +0 -1
- data/fixtures/rails_4_1_8/config/application.rb +0 -15
- data/fixtures/rails_4_1_8/config/boot.rb +0 -4
- data/fixtures/rails_4_1_8/config/environment.rb +0 -5
- data/fixtures/rails_4_1_8/config/environments/test.rb +0 -10
- data/fixtures/rails_4_1_8/config/initializers/secure_headers.rb +0 -16
- data/fixtures/rails_4_1_8/config/routes.rb +0 -4
- data/fixtures/rails_4_1_8/config/script_hashes.yml +0 -5
- data/fixtures/rails_4_1_8/config/secrets.yml +0 -22
- data/fixtures/rails_4_1_8/config.ru +0 -4
- data/fixtures/rails_4_1_8/lib/assets/.keep +0 -0
- data/fixtures/rails_4_1_8/lib/tasks/.keep +0 -0
- data/fixtures/rails_4_1_8/log/.keep +0 -0
- data/fixtures/rails_4_1_8/spec/controllers/other_things_controller_spec.rb +0 -83
- data/fixtures/rails_4_1_8/spec/controllers/things_controller_spec.rb +0 -59
- data/fixtures/rails_4_1_8/spec/spec_helper.rb +0 -15
- data/fixtures/rails_4_1_8/vendor/assets/javascripts/.keep +0 -0
- data/fixtures/rails_4_1_8/vendor/assets/stylesheets/.keep +0 -0
- data/lib/secure_headers/controller_extension.rb +0 -158
- data/lib/secure_headers/hash_helper.rb +0 -7
- data/lib/secure_headers/header.rb +0 -5
- data/lib/secure_headers/headers/content_security_policy/script_hash_middleware.rb +0 -22
- data/lib/secure_headers/version.rb +0 -3
- data/lib/tasks/tasks.rake +0 -48
- data/spec/lib/secure_headers/headers/content_security_policy/script_hash_middleware_spec.rb +0 -46
|
@@ -1,63 +1,62 @@
|
|
|
1
1
|
module SecureHeaders
|
|
2
|
-
class
|
|
3
|
-
class PublicKeyPins
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
end
|
|
2
|
+
class PublicKeyPinsConfigError < StandardError; end
|
|
3
|
+
class PublicKeyPins
|
|
4
|
+
HEADER_NAME = "Public-Key-Pins".freeze
|
|
5
|
+
REPORT_ONLY = "Public-Key-Pins-Report-Only".freeze
|
|
6
|
+
HASH_ALGORITHMS = [:sha256].freeze
|
|
7
|
+
CONFIG_KEY = :hpkp
|
|
8
|
+
|
|
9
|
+
|
|
11
10
|
class << self
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
# Public: make an hpkp header name, value pair
|
|
12
|
+
#
|
|
13
|
+
# Returns nil if not configured, returns header name and value if configured.
|
|
14
|
+
def make_header(config)
|
|
15
|
+
return if config.nil?
|
|
16
|
+
header = new(config)
|
|
17
|
+
[header.name, header.value]
|
|
14
18
|
end
|
|
15
|
-
end
|
|
16
|
-
include Constants
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
def validate_config!(config)
|
|
21
|
+
return if config.nil? || config == OPT_OUT
|
|
22
|
+
raise PublicKeyPinsConfigError.new("config must be a hash.") unless config.is_a? Hash
|
|
23
|
+
|
|
24
|
+
if !config[:max_age]
|
|
25
|
+
raise PublicKeyPinsConfigError.new("max-age is a required directive.")
|
|
26
|
+
elsif config[:max_age].to_s !~ /\A\d+\z/
|
|
27
|
+
raise PublicKeyPinsConfigError.new("max-age must be a number.
|
|
28
|
+
#{config[:max_age]} was supplied.")
|
|
29
|
+
elsif config[:pins] && config[:pins].length < 2
|
|
30
|
+
raise PublicKeyPinsConfigError.new("A minimum of 2 pins are required.")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
20
34
|
|
|
21
|
-
|
|
22
|
-
@
|
|
23
|
-
@
|
|
24
|
-
@
|
|
25
|
-
@
|
|
26
|
-
@
|
|
35
|
+
def initialize(config)
|
|
36
|
+
@max_age = config.fetch(:max_age, nil)
|
|
37
|
+
@pins = config.fetch(:pins, nil)
|
|
38
|
+
@report_uri = config.fetch(:report_uri, nil)
|
|
39
|
+
@report_only = !!config.fetch(:report_only, nil)
|
|
40
|
+
@include_subdomains = !!config.fetch(:include_subdomains, nil)
|
|
27
41
|
end
|
|
28
42
|
|
|
29
43
|
def name
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
44
|
+
if @report_only
|
|
45
|
+
REPORT_ONLY
|
|
46
|
+
else
|
|
47
|
+
HEADER_NAME
|
|
33
48
|
end
|
|
34
|
-
base
|
|
35
49
|
end
|
|
36
50
|
|
|
37
51
|
def value
|
|
38
52
|
header_value = [
|
|
39
|
-
|
|
53
|
+
max_age_directive,
|
|
40
54
|
pin_directives,
|
|
41
55
|
report_uri_directive,
|
|
42
56
|
subdomain_directive
|
|
43
57
|
].compact.join('; ').strip
|
|
44
58
|
end
|
|
45
59
|
|
|
46
|
-
def validate_config(config)
|
|
47
|
-
raise PublicKeyPinsBuildError.new("config must be a hash.") unless config.is_a? Hash
|
|
48
|
-
|
|
49
|
-
if !config[:max_age]
|
|
50
|
-
raise PublicKeyPinsBuildError.new("max-age is a required directive.")
|
|
51
|
-
elsif config[:max_age].to_s !~ /\A\d+\z/
|
|
52
|
-
raise PublicKeyPinsBuildError.new("max-age must be a number.
|
|
53
|
-
#{config[:max_age]} was supplied.")
|
|
54
|
-
elsif config[:pins] && config[:pins].length < 2
|
|
55
|
-
raise PublicKeyPinsBuildError.new("A minimum of 2 pins are required.")
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
config
|
|
59
|
-
end
|
|
60
|
-
|
|
61
60
|
def pin_directives
|
|
62
61
|
return nil if @pins.nil?
|
|
63
62
|
@pins.collect do |pin|
|
|
@@ -67,28 +66,14 @@ module SecureHeaders
|
|
|
67
66
|
end.join('; ')
|
|
68
67
|
end
|
|
69
68
|
|
|
70
|
-
def
|
|
71
|
-
|
|
72
|
-
build_directive(directive_name) if @config[directive_name]
|
|
73
|
-
end.join('; ')
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def build_directive(key)
|
|
77
|
-
"#{self.class.symbol_to_hyphen_case(key)}=#{@config[key]}"
|
|
69
|
+
def max_age_directive
|
|
70
|
+
"max-age=#{@max_age}" if @max_age
|
|
78
71
|
end
|
|
79
72
|
|
|
80
73
|
def report_uri_directive
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if @tag_report_uri
|
|
84
|
-
@report_uri = "#{@report_uri}?enforce=#{@enforce}"
|
|
85
|
-
@report_uri += "&app_name=#{@app_name}" if @app_name
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
"report-uri=\"#{@report_uri}\""
|
|
74
|
+
"report-uri=\"#{@report_uri}\"" if @report_uri
|
|
89
75
|
end
|
|
90
76
|
|
|
91
|
-
|
|
92
77
|
def subdomain_directive
|
|
93
78
|
@include_subdomains ? 'includeSubDomains' : nil
|
|
94
79
|
end
|
|
@@ -1,55 +1,27 @@
|
|
|
1
1
|
module SecureHeaders
|
|
2
|
-
class
|
|
3
|
-
|
|
4
|
-
class StrictTransportSecurity
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def name
|
|
21
|
-
return HSTS_HEADER_NAME
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def value
|
|
25
|
-
case @config
|
|
26
|
-
when String
|
|
27
|
-
return @config
|
|
28
|
-
when NilClass
|
|
29
|
-
return DEFAULT_VALUE
|
|
2
|
+
class STSConfigError < StandardError; end
|
|
3
|
+
|
|
4
|
+
class StrictTransportSecurity
|
|
5
|
+
HEADER_NAME = 'Strict-Transport-Security'
|
|
6
|
+
HSTS_MAX_AGE = "631138519"
|
|
7
|
+
DEFAULT_VALUE = "max-age=" + HSTS_MAX_AGE
|
|
8
|
+
VALID_STS_HEADER = /\Amax-age=\d+(; includeSubdomains)?(; preload)?\z/i
|
|
9
|
+
MESSAGE = "The config value supplied for the HSTS header was invalid. Must match #{VALID_STS_HEADER}"
|
|
10
|
+
CONFIG_KEY = :hsts
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
# Public: generate an hsts header name, value pair.
|
|
14
|
+
#
|
|
15
|
+
# Returns a default header if no configuration is provided, or a
|
|
16
|
+
# header name and value based on the config.
|
|
17
|
+
def make_header(config = nil)
|
|
18
|
+
[HEADER_NAME, config || DEFAULT_VALUE]
|
|
30
19
|
end
|
|
31
20
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
value
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
private
|
|
41
|
-
|
|
42
|
-
def validate_config
|
|
43
|
-
if @config.is_a? Hash
|
|
44
|
-
warn "[DEPRECATION] secure_headers 3.0 will only accept string values for StrictTransportSecurity config"
|
|
45
|
-
if !@config[:max_age]
|
|
46
|
-
raise STSBuildError.new("No max-age was supplied.")
|
|
47
|
-
elsif @config[:max_age].to_s !~ /\A\d+\z/
|
|
48
|
-
raise STSBuildError.new("max-age must be a number. #{@config[:max_age]} was supplied.")
|
|
49
|
-
end
|
|
50
|
-
else
|
|
51
|
-
@config = @config.to_s
|
|
52
|
-
raise STSBuildError.new(MESSAGE) unless @config =~ VALID_STS_HEADER
|
|
21
|
+
def validate_config!(config)
|
|
22
|
+
return if config.nil? || config == OPT_OUT
|
|
23
|
+
raise TypeError.new("Must be a string. Found #{config.class}: #{config} #{config.class}") unless config.is_a?(String)
|
|
24
|
+
raise STSConfigError.new(MESSAGE) unless config =~ VALID_STS_HEADER
|
|
53
25
|
end
|
|
54
26
|
end
|
|
55
27
|
end
|
|
@@ -1,41 +1,26 @@
|
|
|
1
1
|
module SecureHeaders
|
|
2
|
-
class
|
|
2
|
+
class XContentTypeOptionsConfigError < StandardError; end
|
|
3
3
|
# IE only
|
|
4
|
-
class XContentTypeOptions
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
CONFIG_KEY = :x_content_type_options
|
|
9
|
-
end
|
|
10
|
-
include Constants
|
|
11
|
-
|
|
12
|
-
def initialize(config=nil)
|
|
13
|
-
@config = config
|
|
14
|
-
validate_config unless @config.nil?
|
|
15
|
-
end
|
|
4
|
+
class XContentTypeOptions
|
|
5
|
+
HEADER_NAME = "X-Content-Type-Options"
|
|
6
|
+
DEFAULT_VALUE = "nosniff"
|
|
7
|
+
CONFIG_KEY = :x_content_type_options
|
|
16
8
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
DEFAULT_VALUE
|
|
25
|
-
when String
|
|
26
|
-
@config
|
|
27
|
-
else
|
|
28
|
-
warn "[DEPRECATION] secure_headers 3.0 will only accept string values for XContentTypeOptions config"
|
|
29
|
-
@config[:value]
|
|
9
|
+
class << self
|
|
10
|
+
# Public: generate an X-Content-Type-Options header.
|
|
11
|
+
#
|
|
12
|
+
# Returns a default header if no configuration is provided, or a
|
|
13
|
+
# header name and value based on the config.
|
|
14
|
+
def make_header(config = nil)
|
|
15
|
+
[HEADER_NAME, config || DEFAULT_VALUE]
|
|
30
16
|
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
private
|
|
34
17
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
18
|
+
def validate_config!(config)
|
|
19
|
+
return if config.nil? || config == OPT_OUT
|
|
20
|
+
raise TypeError.new("Must be a string. Found #{config.class}: #{config}") unless config.is_a?(String)
|
|
21
|
+
unless config.casecmp(DEFAULT_VALUE) == 0
|
|
22
|
+
raise XContentTypeOptionsConfigError.new("Value can only be nil or 'nosniff'")
|
|
23
|
+
end
|
|
39
24
|
end
|
|
40
25
|
end
|
|
41
26
|
end
|
|
@@ -1,40 +1,25 @@
|
|
|
1
1
|
module SecureHeaders
|
|
2
|
-
class
|
|
3
|
-
class XDownloadOptions
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
CONFIG_KEY = :x_download_options
|
|
8
|
-
end
|
|
9
|
-
include Constants
|
|
10
|
-
|
|
11
|
-
def initialize(config = nil)
|
|
12
|
-
@config = config
|
|
13
|
-
validate_config unless @config.nil?
|
|
14
|
-
end
|
|
2
|
+
class XDOConfigError < StandardError; end
|
|
3
|
+
class XDownloadOptions
|
|
4
|
+
HEADER_NAME = "X-Download-Options"
|
|
5
|
+
DEFAULT_VALUE = 'noopen'
|
|
6
|
+
CONFIG_KEY = :x_download_options
|
|
15
7
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
DEFAULT_VALUE
|
|
24
|
-
when String
|
|
25
|
-
@config
|
|
26
|
-
else
|
|
27
|
-
warn "[DEPRECATION] secure_headers 3.0 will only accept string values for XDownloadOptions config"
|
|
28
|
-
@config[:value]
|
|
8
|
+
class << self
|
|
9
|
+
# Public: generate an X-Download-Options header.
|
|
10
|
+
#
|
|
11
|
+
# Returns a default header if no configuration is provided, or a
|
|
12
|
+
# header name and value based on the config.
|
|
13
|
+
def make_header(config = nil)
|
|
14
|
+
[HEADER_NAME, config || DEFAULT_VALUE]
|
|
29
15
|
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
private
|
|
33
16
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
17
|
+
def validate_config!(config)
|
|
18
|
+
return if config.nil? || config == OPT_OUT
|
|
19
|
+
raise TypeError.new("Must be a string. Found #{config.class}: #{config}") unless config.is_a?(String)
|
|
20
|
+
unless config.casecmp(DEFAULT_VALUE) == 0
|
|
21
|
+
raise XDOConfigError.new("Value can only be nil or 'noopen'")
|
|
22
|
+
end
|
|
38
23
|
end
|
|
39
24
|
end
|
|
40
25
|
end
|
|
@@ -1,41 +1,31 @@
|
|
|
1
1
|
module SecureHeaders
|
|
2
|
-
class
|
|
3
|
-
class XFrameOptions
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def initialize(config = nil)
|
|
13
|
-
@config = config
|
|
14
|
-
validate_config unless @config.nil?
|
|
15
|
-
end
|
|
2
|
+
class XFOConfigError < StandardError; end
|
|
3
|
+
class XFrameOptions
|
|
4
|
+
HEADER_NAME = "X-Frame-Options"
|
|
5
|
+
CONFIG_KEY = :x_frame_options
|
|
6
|
+
SAMEORIGIN = "sameorigin"
|
|
7
|
+
DENY = "deny"
|
|
8
|
+
ALLOW_FROM = "allow-from"
|
|
9
|
+
ALLOW_ALL = "allowall"
|
|
10
|
+
DEFAULT_VALUE = SAMEORIGIN
|
|
11
|
+
VALID_XFO_HEADER = /\A(#{SAMEORIGIN}\z|#{DENY}\z|#{ALLOW_ALL}\z|#{ALLOW_FROM}[:\s])/i
|
|
16
12
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
DEFAULT_VALUE
|
|
25
|
-
when String
|
|
26
|
-
@config
|
|
27
|
-
else
|
|
28
|
-
warn "[DEPRECATION] secure_headers 3.0 will only accept string values for XFrameOptions config"
|
|
29
|
-
@config[:value]
|
|
13
|
+
class << self
|
|
14
|
+
# Public: generate an X-Frame-Options header.
|
|
15
|
+
#
|
|
16
|
+
# Returns a default header if no configuration is provided, or a
|
|
17
|
+
# header name and value based on the config.
|
|
18
|
+
def make_header(config = nil)
|
|
19
|
+
return if config == OPT_OUT
|
|
20
|
+
[HEADER_NAME, config || DEFAULT_VALUE]
|
|
30
21
|
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
private
|
|
34
22
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
23
|
+
def validate_config!(config)
|
|
24
|
+
return if config.nil? || config == OPT_OUT
|
|
25
|
+
raise TypeError.new("Must be a string. Found #{config.class}: #{config}") unless config.is_a?(String)
|
|
26
|
+
unless config =~ VALID_XFO_HEADER
|
|
27
|
+
raise XFOConfigError.new("Value must be SAMEORIGIN|DENY|ALLOW-FROM:|ALLOWALL")
|
|
28
|
+
end
|
|
39
29
|
end
|
|
40
30
|
end
|
|
41
31
|
end
|
|
@@ -1,41 +1,26 @@
|
|
|
1
1
|
module SecureHeaders
|
|
2
|
-
class
|
|
3
|
-
class XPermittedCrossDomainPolicies
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
CONFIG_KEY = :x_permitted_cross_domain_policies
|
|
9
|
-
end
|
|
10
|
-
include Constants
|
|
11
|
-
|
|
12
|
-
def initialize(config = nil)
|
|
13
|
-
@config = config
|
|
14
|
-
validate_config unless @config.nil?
|
|
15
|
-
end
|
|
2
|
+
class XPCDPConfigError < StandardError; end
|
|
3
|
+
class XPermittedCrossDomainPolicies
|
|
4
|
+
HEADER_NAME = "X-Permitted-Cross-Domain-Policies"
|
|
5
|
+
DEFAULT_VALUE = 'none'
|
|
6
|
+
VALID_POLICIES = %w(all none master-only by-content-type by-ftp-filename)
|
|
7
|
+
CONFIG_KEY = :x_permitted_cross_domain_policies
|
|
16
8
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
DEFAULT_VALUE
|
|
25
|
-
when String
|
|
26
|
-
@config
|
|
27
|
-
else
|
|
28
|
-
warn "[DEPRECATION] secure_headers 3.0 will only accept string values for XPermittedCrossDomainPolicies config"
|
|
29
|
-
@config[:value]
|
|
9
|
+
class << self
|
|
10
|
+
# Public: generate an X-Permitted-Cross-Domain-Policies header.
|
|
11
|
+
#
|
|
12
|
+
# Returns a default header if no configuration is provided, or a
|
|
13
|
+
# header name and value based on the config.
|
|
14
|
+
def make_header(config = nil)
|
|
15
|
+
[HEADER_NAME, config || DEFAULT_VALUE]
|
|
30
16
|
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
private
|
|
34
17
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
18
|
+
def validate_config!(config)
|
|
19
|
+
return if config.nil? || config == OPT_OUT
|
|
20
|
+
raise TypeError.new("Must be a string. Found #{config.class}: #{config}") unless config.is_a?(String)
|
|
21
|
+
unless VALID_POLICIES.include?(config.downcase)
|
|
22
|
+
raise XPCDPConfigError.new("Value can only be one of #{VALID_POLICIES.join(', ')}")
|
|
23
|
+
end
|
|
39
24
|
end
|
|
40
25
|
end
|
|
41
26
|
end
|
|
@@ -1,55 +1,24 @@
|
|
|
1
1
|
module SecureHeaders
|
|
2
|
-
class
|
|
3
|
-
class XXssProtection
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
CONFIG_KEY = :x_xss_protection
|
|
9
|
-
end
|
|
10
|
-
include Constants
|
|
11
|
-
|
|
12
|
-
def initialize(config=nil)
|
|
13
|
-
@config = config
|
|
14
|
-
validate_config unless @config.nil?
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def name
|
|
18
|
-
X_XSS_PROTECTION_HEADER_NAME
|
|
19
|
-
end
|
|
2
|
+
class XXssProtectionConfigError < StandardError; end
|
|
3
|
+
class XXssProtection
|
|
4
|
+
HEADER_NAME = 'X-XSS-Protection'
|
|
5
|
+
DEFAULT_VALUE = "1; mode=block"
|
|
6
|
+
VALID_X_XSS_HEADER = /\A[01](; mode=block)?(; report=.*)?\z/i
|
|
7
|
+
CONFIG_KEY = :x_xss_protection
|
|
20
8
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
warn "[DEPRECATION] secure_headers 3.0 will only accept string values for XXssProtection config"
|
|
29
|
-
value = @config[:value].to_s
|
|
30
|
-
value += "; mode=#{@config[:mode]}" if @config[:mode]
|
|
31
|
-
value += "; report=#{@config[:report_uri]}" if @config[:report_uri]
|
|
32
|
-
value
|
|
9
|
+
class << self
|
|
10
|
+
# Public: generate an X-Xss-Protection header.
|
|
11
|
+
#
|
|
12
|
+
# Returns a default header if no configuration is provided, or a
|
|
13
|
+
# header name and value based on the config.
|
|
14
|
+
def make_header(config = nil)
|
|
15
|
+
[HEADER_NAME, config || DEFAULT_VALUE]
|
|
33
16
|
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
private
|
|
37
|
-
|
|
38
|
-
def validate_config
|
|
39
|
-
if @config.is_a? Hash
|
|
40
|
-
if !@config[:value]
|
|
41
|
-
raise XXssProtectionBuildError.new(":value key is missing")
|
|
42
|
-
elsif @config[:value]
|
|
43
|
-
unless [0,1].include?(@config[:value].to_i)
|
|
44
|
-
raise XXssProtectionBuildError.new(":value must be 1 or 0")
|
|
45
|
-
end
|
|
46
17
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
elsif @config.is_a? String
|
|
52
|
-
raise XXssProtectionBuildError.new("Invalid format (see VALID_X_XSS_HEADER)") unless @config =~ VALID_X_XSS_HEADER
|
|
18
|
+
def validate_config!(config)
|
|
19
|
+
return if config.nil? || config == OPT_OUT
|
|
20
|
+
raise TypeError.new("Must be a string. Found #{config.class}: #{config}") unless config.is_a?(String)
|
|
21
|
+
raise XXssProtectionConfigError.new("Invalid format (see VALID_X_XSS_HEADER)") unless config.to_s =~ VALID_X_XSS_HEADER
|
|
53
22
|
end
|
|
54
23
|
end
|
|
55
24
|
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module SecureHeaders
|
|
2
|
+
class Middleware
|
|
3
|
+
def initialize(app)
|
|
4
|
+
@app = app
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# merges the hash of headers into the current header set.
|
|
8
|
+
def call(env)
|
|
9
|
+
req = Rack::Request.new(env)
|
|
10
|
+
status, headers, response = @app.call(env)
|
|
11
|
+
headers.merge!(SecureHeaders.header_hash_for(req))
|
|
12
|
+
[status, headers, response]
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -5,10 +5,9 @@ module SecureHeaders
|
|
|
5
5
|
# Main class that register this extension.
|
|
6
6
|
#
|
|
7
7
|
def registered(app)
|
|
8
|
-
app.extend SecureHeaders::ClassMethods
|
|
9
8
|
app.helpers SecureHeaders::InstanceMethods
|
|
10
9
|
end
|
|
11
|
-
|
|
10
|
+
alias_method :included, :registered
|
|
12
11
|
end
|
|
13
12
|
end
|
|
14
13
|
end
|
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
# rails 3.1+
|
|
2
2
|
if defined?(Rails::Railtie)
|
|
3
3
|
module SecureHeaders
|
|
4
|
-
class Railtie < Rails::
|
|
5
|
-
isolate_namespace
|
|
4
|
+
class Railtie < Rails::Railtie
|
|
5
|
+
isolate_namespace SecureHeaders if defined? isolate_namespace # rails 3.0
|
|
6
6
|
conflicting_headers = ['X-Frame-Options', 'X-XSS-Protection', 'X-Content-Type-Options',
|
|
7
7
|
'X-Permitted-Cross-Domain-Policies', 'X-Download-Options',
|
|
8
8
|
'X-Content-Type-Options', 'Strict-Transport-Security',
|
|
9
9
|
'Content-Security-Policy', 'Content-Security-Policy-Report-Only',
|
|
10
|
-
'X-Permitted-Cross-Domain-Policies','Public-Key-Pins','Public-Key-Pins-Report-Only']
|
|
10
|
+
'X-Permitted-Cross-Domain-Policies', 'Public-Key-Pins', 'Public-Key-Pins-Report-Only']
|
|
11
|
+
|
|
12
|
+
initializer "secure_headers.middleware" do
|
|
13
|
+
Rails.application.config.middleware.use SecureHeaders::Middleware
|
|
14
|
+
end
|
|
11
15
|
|
|
12
16
|
initializer "secure_headers.action_controller" do
|
|
13
17
|
ActiveSupport.on_load(:action_controller) do
|
|
14
|
-
include
|
|
18
|
+
include SecureHeaders
|
|
15
19
|
|
|
16
20
|
unless Rails.application.config.action_dispatch.default_headers.nil?
|
|
17
21
|
conflicting_headers.each do |header|
|
|
18
22
|
Rails.application.config.action_dispatch.default_headers.delete(header)
|
|
19
23
|
end
|
|
20
24
|
end
|
|
21
|
-
|
|
22
25
|
end
|
|
23
26
|
end
|
|
24
27
|
end
|
|
@@ -26,7 +29,7 @@ if defined?(Rails::Railtie)
|
|
|
26
29
|
else
|
|
27
30
|
module ActionController
|
|
28
31
|
class Base
|
|
29
|
-
include
|
|
32
|
+
include SecureHeaders
|
|
30
33
|
end
|
|
31
34
|
end
|
|
32
35
|
end
|