http-security 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +21 -0
  5. data/.yardopts +1 -0
  6. data/ChangeLog.md +17 -0
  7. data/Gemfile +17 -0
  8. data/LICENSE.txt +20 -0
  9. data/README.md +90 -0
  10. data/Rakefile +34 -0
  11. data/http-security.gemspec +23 -0
  12. data/lib/http/security.rb +2 -0
  13. data/lib/http/security/exceptions.rb +8 -0
  14. data/lib/http/security/headers.rb +12 -0
  15. data/lib/http/security/headers/cache_control.rb +36 -0
  16. data/lib/http/security/headers/content_security_policy.rb +71 -0
  17. data/lib/http/security/headers/content_security_policy_report_only.rb +10 -0
  18. data/lib/http/security/headers/pragma.rb +24 -0
  19. data/lib/http/security/headers/public_key_pins.rb +60 -0
  20. data/lib/http/security/headers/public_key_pins_report_only.rb +10 -0
  21. data/lib/http/security/headers/set_cookie.rb +75 -0
  22. data/lib/http/security/headers/strict_transport_security.rb +29 -0
  23. data/lib/http/security/headers/x_content_type_options.rb +24 -0
  24. data/lib/http/security/headers/x_frame_options.rb +39 -0
  25. data/lib/http/security/headers/x_permitted_cross_domain_policies.rb +47 -0
  26. data/lib/http/security/headers/x_xss_protection.rb +34 -0
  27. data/lib/http/security/http_date.rb +13 -0
  28. data/lib/http/security/malformed_header.rb +33 -0
  29. data/lib/http/security/parsers.rb +14 -0
  30. data/lib/http/security/parsers/cache_control.rb +62 -0
  31. data/lib/http/security/parsers/content_security_policy.rb +128 -0
  32. data/lib/http/security/parsers/content_security_policy_report_only.rb +10 -0
  33. data/lib/http/security/parsers/expires.rb +19 -0
  34. data/lib/http/security/parsers/parser.rb +408 -0
  35. data/lib/http/security/parsers/pragma.rb +25 -0
  36. data/lib/http/security/parsers/public_key_pins.rb +43 -0
  37. data/lib/http/security/parsers/public_key_pins_report_only.rb +10 -0
  38. data/lib/http/security/parsers/set_cookie.rb +62 -0
  39. data/lib/http/security/parsers/strict_transport_security.rb +42 -0
  40. data/lib/http/security/parsers/x_content_type_options.rb +19 -0
  41. data/lib/http/security/parsers/x_frame_options.rb +47 -0
  42. data/lib/http/security/parsers/x_permitted_cross_domain_policies.rb +33 -0
  43. data/lib/http/security/parsers/x_xss_protection.rb +27 -0
  44. data/lib/http/security/response.rb +323 -0
  45. data/lib/http/security/version.rb +5 -0
  46. data/spec/data/alexa.csv +100 -0
  47. data/spec/headers/cache_control_spec.rb +40 -0
  48. data/spec/headers/content_security_policy_spec.rb +46 -0
  49. data/spec/headers/pragma_spec.rb +26 -0
  50. data/spec/headers/public_key_pins_spec.rb +68 -0
  51. data/spec/headers/set_cookie_spec.rb +122 -0
  52. data/spec/headers/strict_transport_security_spec.rb +39 -0
  53. data/spec/headers/x_content_type_options_spec.rb +26 -0
  54. data/spec/headers/x_frame_options_spec.rb +86 -0
  55. data/spec/headers/x_permitted_cross_domain_policies_spec.rb +108 -0
  56. data/spec/headers/x_xss_protection_spec.rb +59 -0
  57. data/spec/parsers/cache_control_spec.rb +26 -0
  58. data/spec/parsers/content_security_policy_report_only_spec.rb +48 -0
  59. data/spec/parsers/content_security_policy_spec.rb +74 -0
  60. data/spec/parsers/expires_spec.rb +71 -0
  61. data/spec/parsers/parser_spec.rb +317 -0
  62. data/spec/parsers/pragma_spec.rb +10 -0
  63. data/spec/parsers/public_key_pins_spec.rb +81 -0
  64. data/spec/parsers/set_cookie_spec.rb +55 -0
  65. data/spec/parsers/strict_transport_security_spec.rb +62 -0
  66. data/spec/parsers/x_content_type_options_spec.rb +10 -0
  67. data/spec/parsers/x_frame_options_spec.rb +24 -0
  68. data/spec/parsers/x_permitted_cross_domain_policies_spec.rb +34 -0
  69. data/spec/parsers/x_xss_protection_spec.rb +39 -0
  70. data/spec/response_spec.rb +262 -0
  71. data/spec/spec_helper.rb +13 -0
  72. data/tasks/alexa.rb +40 -0
  73. metadata +171 -0
@@ -0,0 +1,10 @@
1
+ require 'http/security/headers/public_key_pins'
2
+
3
+ module HTTP
4
+ module Security
5
+ module Headers
6
+ class PublicKeyPinsReportOnly < PublicKeyPins
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,75 @@
1
+ module HTTP
2
+ module Security
3
+ module Headers
4
+ class SetCookie
5
+
6
+ include Enumerable
7
+
8
+ # @return [Array<Cookie>]
9
+ attr_reader :cookies
10
+
11
+ class Cookie
12
+
13
+ attr_reader :cookie
14
+
15
+ attr_reader :path
16
+
17
+ attr_reader :domain
18
+
19
+ attr_reader :expires
20
+
21
+ def initialize(directives={})
22
+ @cookie = directives[:cookie]
23
+ @path = directives[:path]
24
+ @domain = directives[:domain]
25
+ @expires = directives[:expires]
26
+ @secure = directives[:secure]
27
+ @http_only = directives[:http_only]
28
+ end
29
+
30
+ def name
31
+ @cookie.keys.first
32
+ end
33
+
34
+ def value
35
+ @cookie.values.first
36
+ end
37
+
38
+ def secure?
39
+ !!@secure
40
+ end
41
+
42
+ def http_only?
43
+ !!@http_only
44
+ end
45
+
46
+ def to_s
47
+ str = "#{name}=#{value}"
48
+
49
+ str << "; Path=#{@path}" if @path
50
+ str << "; Domain=#{@domain}" if @domain
51
+ str << "; Expires=#{@expires.httpdate}" if @expires
52
+ str << "; Secure" if @secure
53
+ str << "; HttpOnly" if @http_only
54
+
55
+ return str
56
+ end
57
+
58
+ end
59
+
60
+ def initialize(cookies=[])
61
+ @cookies = cookies.map { |cookie| Cookie.new(cookie) }
62
+ end
63
+
64
+ def each(&block)
65
+ @cookies.each(&block)
66
+ end
67
+
68
+ def to_s
69
+ @cookies.map(&:to_s).join(', ')
70
+ end
71
+
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,29 @@
1
+ module HTTP
2
+ module Security
3
+ module Headers
4
+ class StrictTransportSecurity
5
+
6
+ attr_reader :max_age
7
+
8
+ def initialize(directives={})
9
+ @max_age = directives[:max_age]
10
+ @include_sub_domains = directives[:includesubdomains]
11
+ end
12
+
13
+ def include_sub_domains?
14
+ !!@include_sub_domains
15
+ end
16
+
17
+ def to_s
18
+ directives = []
19
+
20
+ directives << "max-age=#{@max_age}" if @max_age
21
+ directives << "includeSubDomains" if @include_sub_domains
22
+
23
+ return directives.join('; ')
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,24 @@
1
+ module HTTP
2
+ module Security
3
+ module Headers
4
+ class XContentTypeOptions
5
+
6
+ def initialize(directives={})
7
+ @no_sniff = directives[:nosniff]
8
+ end
9
+
10
+ def no_sniff?
11
+ !!@no_sniff
12
+ end
13
+
14
+ def to_s
15
+ str = ''
16
+ str << "nosniff" if @no_sniff
17
+
18
+ return str
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,39 @@
1
+ module HTTP
2
+ module Security
3
+ module Headers
4
+ class XFrameOptions
5
+
6
+ attr_reader :allow_from
7
+
8
+ def initialize(directives={})
9
+ @deny = directives[:deny]
10
+ @same_origin = directives[:sameorigin]
11
+ @allow_from = directives[:allow_from]
12
+ @allow_all = directives[:allowall]
13
+ end
14
+
15
+ def deny?
16
+ !!@deny
17
+ end
18
+
19
+ def same_origin?
20
+ !!@same_origin
21
+ end
22
+
23
+ def allow_all?
24
+ !!@allow_all
25
+ end
26
+
27
+ def to_s
28
+ if @deny then 'DENY'
29
+ elsif @same_origin then 'SAMEORIGIN'
30
+ elsif @allow_from then "ALLOW-FROM #{@allow_from}"
31
+ elsif @allow_all then 'ALLOWALL'
32
+ else ''
33
+ end
34
+ end
35
+
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,47 @@
1
+ module HTTP
2
+ module Security
3
+ module Headers
4
+ class XPermittedCrossDomainPolicies
5
+
6
+ def initialize(directives={})
7
+ @none = directives[:none]
8
+ @master_only = directives[:master_only]
9
+ @by_content_type = directives[:by_content_type]
10
+ @by_ftp_filename = directives[:by_ftp_filename]
11
+ @all = directives[:all]
12
+ end
13
+
14
+ def none?
15
+ !!@none
16
+ end
17
+
18
+ def master_only?
19
+ !!@master_only
20
+ end
21
+
22
+ def by_content_type?
23
+ !!@by_content_type
24
+ end
25
+
26
+ def by_ftp_filename?
27
+ !!@by_ftp_filename
28
+ end
29
+
30
+ def all?
31
+ !!@all
32
+ end
33
+
34
+ def to_s
35
+ if @none then 'none'
36
+ elsif @master_only then 'master-only'
37
+ elsif @by_content_type then 'by-content-type'
38
+ elsif @by_ftp_filename then 'by-ftp-filename'
39
+ elsif @all then 'all'
40
+ else ''
41
+ end
42
+ end
43
+
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,34 @@
1
+ module HTTP
2
+ module Security
3
+ module Headers
4
+ class XXSSProtection
5
+
6
+ attr_reader :mode
7
+
8
+ attr_reader :report
9
+
10
+ def initialize(directives={})
11
+ @enabled = directives[:enabled]
12
+ @mode = directives[:mode]
13
+ @report = directives[:report]
14
+ end
15
+
16
+ def enabled?
17
+ !!@enabled
18
+ end
19
+
20
+ def to_s
21
+ str = if @enabled then '1'
22
+ else '0'
23
+ end
24
+
25
+ str << "; mode=#{@mode}" if @mode
26
+ str << "; report=#{@report}" if @report
27
+
28
+ return str
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,13 @@
1
+ require 'date'
2
+
3
+ module HTTP
4
+ module Security
5
+ class HTTPDate < Date
6
+
7
+ def to_s
8
+ httpdate
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,33 @@
1
+ module HTTP
2
+ module Security
3
+ class MalformedHeader
4
+
5
+ # Raw value of the header.
6
+ #
7
+ # @return [String]
8
+ attr_reader :value
9
+
10
+ # Cause of the parser failure.
11
+ #
12
+ # @return [Parslet::Cause]
13
+ attr_reader :cause
14
+
15
+ #
16
+ # Initializes the malformed header.
17
+ #
18
+ # @param [String] value
19
+ # The raw header value.
20
+ #
21
+ # @param [Parslet::Cause] cause
22
+ # The cause of the parser failure.
23
+ #
24
+ def initialize(value,cause)
25
+ @value = value
26
+ @cause = cause
27
+ end
28
+
29
+ alias value to_s
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,14 @@
1
+ require 'http/security/parsers/cache_control'
2
+ require 'http/security/parsers/content_security_policy'
3
+ require 'http/security/parsers/content_security_policy_report_only'
4
+ require 'http/security/parsers/expires'
5
+ require 'http/security/parsers/pragma'
6
+ require 'http/security/parsers/public_key_pins'
7
+ require 'http/security/parsers/public_key_pins_report_only'
8
+ require 'http/security/parsers/strict_transport_security'
9
+ require 'http/security/parsers/set_cookie'
10
+ require 'http/security/parsers/public_key_pins'
11
+ require 'http/security/parsers/x_content_type_options'
12
+ require 'http/security/parsers/x_frame_options'
13
+ require 'http/security/parsers/x_permitted_cross_domain_policies'
14
+ require 'http/security/parsers/x_xss_protection'
@@ -0,0 +1,62 @@
1
+ require 'http/security/parsers/parser'
2
+
3
+ module HTTP
4
+ module Security
5
+ module Parsers
6
+ class CacheControl < Parser
7
+ # Cache-Control
8
+ # Syntax:
9
+ # Cache-Control = "Cache-Control" ":" 1#cache-directive
10
+ # cache-directive = cache-response-directive
11
+ # cache-response-directive =
12
+ # "public" ; Section 14.9.1
13
+ # | "private" [ "=" <"> 1#field-name <"> ] ; Section 14.9.1
14
+ # | "no-cache" [ "=" <"> 1#field-name <"> ]; Section 14.9.1
15
+ # | "no-store" ; Section 14.9.2
16
+ # | "no-transform" ; Section 14.9.5
17
+ # | "must-revalidate" ; Section 14.9.4
18
+ # | "proxy-revalidate" ; Section 14.9.4
19
+ # | "max-age" "=" delta-seconds ; Section 14.9.3
20
+ # | "s-maxage" "=" delta-seconds ; Section 14.9.3
21
+ # | cache-extension ; Section 14.9.6
22
+ # cache-extension = token [ "=" ( token | quoted-string ) ]
23
+ rule(:cache_control) do
24
+ (
25
+ cache_control_values >> (comma >> cache_control_values).repeat
26
+ ).as(:directives)
27
+ end
28
+ root :cache_control
29
+
30
+ rule(:cache_control_values) do
31
+ cc_public |
32
+ cc_private |
33
+ no_cache |
34
+ no_store |
35
+ no_transform |
36
+ must_revalidate |
37
+ max_age |
38
+ s_maxage |
39
+ only_if_cached |
40
+ header_extension
41
+ end
42
+
43
+ #"private" [ "=" <"> 1#field-name <"> ];
44
+ rule(:cc_public) do
45
+ stri("public").as(:key) >> (equals >> field_name.as(:value)).maybe
46
+ end
47
+
48
+ #"private" [ "=" <"> 1#field-name <"> ];
49
+ rule(:cc_private) do
50
+ stri("private").as(:key) >> (equals >> field_name.as(:value)).maybe
51
+ end
52
+
53
+ field_directive_rule :no_cache, 'no-cache'
54
+ directive_rule :no_store, 'no-store'
55
+ directive_rule :no_transform, 'no-transform'
56
+ directive_rule :must_revalidate, 'must-revalidate'
57
+ directive_rule :only_if_cached, 'only-if-cached'
58
+
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,128 @@
1
+ require 'http/security/parsers/parser'
2
+
3
+ module HTTP
4
+ module Security
5
+ module Parsers
6
+ class ContentSecurityPolicy < Parser
7
+ # Content-Security-Policy
8
+ # Syntax:
9
+ # Content-Security-Policy =
10
+ # policy-token = [ directive-token *( ";" [ directive-token ] ) ]
11
+ # directive-token = *WSP [ directive-name [ WSP directive-value ] ]
12
+ # directive-name = 1*( ALPHA / DIGIT / "-" )
13
+ # directive-value = *( WSP / <VCHAR except ";" and ","> )
14
+ #
15
+ # Parsing Policies:
16
+ # To parse the policy policy, the user agent MUST use an algorithm equivalent to the following:
17
+ # 1. Let the set of directives be the empty set.
18
+ # 2. For each non-empty token returned by strictly splitting the string policy on the character U+003B SEMICOLON (;):
19
+ # 1. Skip whitespace.
20
+ # 2. Collect a sequence of characters that are not space characters. The collected characters are the directive name.
21
+ # 3. If there are characters remaining in token, skip ahead exactly one character (which must be a space character).
22
+ # 4. The remaining characters in token (if any) are the directive value.
23
+ # 5. If the set of directives already contains a directive whose name is a case insensitive match for directive name,
24
+ # ignore this instance of the directive and continue to the next token.
25
+ # 6. Add a directive to the set of directives with name directive name and value directive value.
26
+ # 3. Return the set of directives.
27
+ rule(:csp_pattern) do
28
+ (
29
+ csp_entry >> ( str(";") >> wsp >> csp_entry ).repeat(0) >> semicolon.maybe
30
+ ).as(:directives)
31
+ end
32
+ root :csp_pattern
33
+
34
+ rule(:csp_entry) do
35
+ (csp_directive.as(:key) >> wsp >> source_list.as(:value)) |
36
+ report_uri |
37
+ sandbox
38
+ end
39
+
40
+ rule(:csp_directive) do
41
+ stri("default-src") |
42
+ stri("script-src") |
43
+ stri("object-src") |
44
+ stri("style-src") |
45
+ stri("img-src") |
46
+ stri("media-src") |
47
+ stri("frame-src") |
48
+ stri("font-src") |
49
+ stri("connect-src")
50
+ end
51
+
52
+ # Source list
53
+ # Syntax:
54
+ # source-list = *WSP [ source-expression *( 1*WSP source-expression ) *WSP ]
55
+ # / *WSP "'none'" *WSP
56
+ # source-expression = scheme-source / host-source / keyword-source
57
+ # scheme-source = scheme ":"
58
+ # host-source = [ scheme "://" ] host [ port ]
59
+ # ext-host-source = host-source "/" *( <VCHAR except ";" and ","> )
60
+ # ; ext-host-source is reserved for future use.
61
+ # keyword-source = "'self'" / "'unsafe-inline'" / "'unsafe-eval'"
62
+ # scheme = <scheme production from RFC 3986>
63
+ # host = "*" / [ "*." ] 1*host-char *( "." 1*host-char )
64
+ # host-char = ALPHA / DIGIT / "-"
65
+ # port = ":" ( 1*DIGIT / "*" )
66
+ rule(:source_list) do
67
+ (wsp? >> stri("'none'") >> wsp?) |
68
+ (wsp? >> source_expression >> (wsp >> source_expression).repeat(0))
69
+ end
70
+
71
+ rule(:source_expression) do
72
+ scheme_source | host_source | keyword_source
73
+ end
74
+
75
+ rule(:csp_vchar) do
76
+ match["\x20-\x2b"] |
77
+ match["#{Regexp.escape("\x2d")}-\x3a"] |
78
+ match["\x3c-\x7f"]
79
+ end
80
+
81
+ rule(:scheme_source) do
82
+ (scheme >> str("://")).absent? >> scheme >> str(":")
83
+ end
84
+
85
+ rule(:host_source) do
86
+ (scheme >> str("://")).maybe >> csp_host >> port.maybe
87
+ end
88
+
89
+ rule(:csp_host) do
90
+ (str("*.").maybe >> host_char.repeat(1) >> ( str(".") >> host_char.repeat(1) ).repeat(0)) |
91
+ str("*")
92
+ end
93
+
94
+ rule(:host_char) do
95
+ alnum | str("-")
96
+ end
97
+
98
+ rule(:keyword_source) do
99
+ stri("'self'") | stri("'unsafe-inline'") | stri("'unsafe-eval'")
100
+ end
101
+
102
+ rule(:port) do
103
+ str(":") >> digits.as(:port)
104
+ end
105
+
106
+ rule(:ext_host_source) do
107
+ (scheme >> str("://")).maybe >> csp_host >> ext_host_source.maybe >> port.maybe
108
+ end
109
+
110
+ # report-uri
111
+ # directive-name = "report-uri"
112
+ # directive-value = uri-reference *( 1*WSP uri-reference )
113
+ # uri-reference = <URI-reference from RFC 3986>
114
+ rule(:report_uri) do
115
+ stri("report-uri").as(:key) >> wsp.repeat(1) >> (uri >> (wsp.repeat(1) >> uri).repeat(0)).as(:values)
116
+ end
117
+
118
+ # sandbox (Optional)
119
+ # directive-name = "sandbox"
120
+ # directive-value = token *( 1*WSP token )
121
+ # token = <token from RFC 2616>
122
+ rule(:sandbox) do
123
+ stri("sandbox").as(:key) >> wsp.repeat(1) >> (token >> (wsp.repeat(1) >> token).repeat(0)).as(:value)
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end