secure_headers 2.5.3 → 3.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of secure_headers might be problematic. Click here for more details.
- 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
|