http-security 0.1.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.
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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 097f834633f8dfec6c6946ed5a1d6df6eb099dcf
4
+ data.tar.gz: 593a835d602354c2d1f815c211b743c53038e370
5
+ SHA512:
6
+ metadata.gz: 5b2832989c451f162a1b0e5d41c9bca6be583f28a9d0f69e363ceed62e11b35f034d9a15db62ce35735a0209f6a3a48ccabb9ffdd3e781ba16094a7d7a831b7c
7
+ data.tar.gz: f70ba9dd6f347fc95c635ade11b081dd180a2bdd18a648ea20e80ba939d70a1fec4963927d9ab6786c3d6ae694dd45181c706e82d91da64f4cc7c5f0cc790ba3
@@ -0,0 +1,19 @@
1
+ Gemfile.lock
2
+
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ coverage
7
+ InstalledFiles
8
+ lib/bundler/man
9
+ pkg
10
+ rdoc
11
+ spec/reports
12
+ test/tmp
13
+ test/version_tmp
14
+ tmp
15
+
16
+ # YARD artifacts
17
+ .yardoc
18
+ _yardoc
19
+ doc/
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour --format documentation
@@ -0,0 +1,21 @@
1
+ language: ruby
2
+ sudo: false
3
+ rvm:
4
+ - 1.9.3
5
+ - 2.0
6
+ - 2.1
7
+ - ruby-head
8
+ - jruby-19mode
9
+ - jruby-head
10
+ - rbx-2
11
+ matrix:
12
+ allow_failures:
13
+ - rvm: rbx-2
14
+ - rvm: jruby-19mode
15
+ - rvm: jruby-head
16
+ addons:
17
+ code_climate:
18
+ repo_token: 4e8013e9105e3dd8153da54825ce8fab58c7408dce36fa351ef35c51bb9b0b05
19
+ notifications:
20
+ slack:
21
+ secure: awgcYPnPALqyAn3H58d+qzQYezzEyuqr71GmU9AsXuOa3sGM1JVqmUOqgoXeykBcV8EV5DEO+65fCkc+GnBxyPjnyYyvzW7VEcj3xQdxyVgn9fK0a76Mp+ywoWOPG7t4Z8dIK3PCT12QO+XgGWiLA9YG10eRtGRRD1V2PCKv41Y=
@@ -0,0 +1 @@
1
+ --markup markdown --title "Security Headers Documentation" --protected
@@ -0,0 +1,17 @@
1
+ ### 0.1.0 / 2015-07-09
2
+
3
+ * Initial release:
4
+ * `Cache-Control`
5
+ * `Content-Security-Policy`
6
+ * `Content-Security-Policy-Report-Only`
7
+ * `Expires`
8
+ * `Pragma`
9
+ * `Public-Key-Pins`
10
+ * `Public-Key-Pins-Report-Only`
11
+ * `Set-Cookie`
12
+ * `Strict-Transport-Security`
13
+ * `X-Content-Type-Options`
14
+ * `X-Frame-Options`
15
+ * `X-Permitted-Cross-Domain-Policies`
16
+ * `X-XSS-Protection
17
+
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'nokogiri'
7
+
8
+ gem 'rake'
9
+ gem 'rubygems-tasks', '~> 0.2'
10
+
11
+ gem 'rspec', '~> 3.0'
12
+
13
+ gem 'kramdown'
14
+ gem 'yard', '~> 0.8'
15
+ end
16
+
17
+ gem "codeclimate-test-reporter", group: :test, require: nil
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Trail of Bits
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,90 @@
1
+ # HTTP Security
2
+
3
+ * [Source](https://github.com/trailofbits/http-security)
4
+ * [Issues](https://github.com/trailofbits/http-security/issues)
5
+ * [Documentation](https://rubydoc.info/gems/http-security/frames)
6
+
7
+ [![Code Climate](https://codeclimate.com/github/trailofbits/http-security.png)](https://codeclimate.com/github/trailofbits/http-security) [![Build Status](https://travis-ci.org/trailofbits/http-security.svg)](https://travis-ci.org/trailofbits/http-security) [![Test Coverage](https://codeclimate.com/github/trailofbits/http-security/badges/coverage.svg)](https://codeclimate.com/github/trailofbits/http-security)
8
+
9
+ Security Headers is a parser for security-relevant HTTP headers. Each header
10
+ value is parsed and validated according to the syntax specified in its relevant
11
+ RFC.
12
+
13
+ Security Headers relies on [parslet] for constructing its parsing grammar.
14
+
15
+ Currently parsed security headers are:
16
+
17
+ * `Cache-Control`
18
+ * `Content-Security-Policy`
19
+ * `Content-Security-Policy-Report-Only`
20
+ * `Expires`
21
+ * `Pragma`
22
+ * `Public-Key-Pins`
23
+ * `Public-Key-Pins-Report-Only`
24
+ * `Set-Cookie`
25
+ * `Strict-Transport-Security`
26
+ * `X-Content-Type-Options`
27
+ * `X-Frame-Options`
28
+ * `X-Permitted-Cross-Domain-Policies`
29
+ * `X-XSS-Protection
30
+
31
+ ## Example
32
+
33
+ require 'net/https'
34
+ response = Net::HTTP.get_response(URI('https://twitter.com/'))
35
+
36
+ require 'http/security'
37
+ headers = HTTP::Security::Response.parse(response)
38
+
39
+ headers.cache_control
40
+ # => #<HTTP::Security::Headers::CacheControl:0x00000002f65778 @private=nil, @max_age=nil, @no_cache=true>
41
+
42
+ headers.content_security_policy
43
+ # => #<HTTP::Security::Headers::ContentSecurityPolicy:0x00000002d8e238 @default_src="https:"@12, @script_src="'unsafe-inline' 'unsafe-eval' https:"@172, @object_src="https:"@153, @style_src="'unsafe-inline' https:"@220, @img_src="https: blob: data:"@98, @media_src="https: blob:"@128, @frame_src="https: twitter:"@73, @font_src="https: data:"@49, @connect_src="https:"@32, @report_uri=[#<URI::HTTPS:0x00000002d94250 URL:https://twitter.com/i/csp_report?a=NVQWGYLXFVZXO2LGOQ%3D%3D%3D%3D%3D%3D&ro=false;>], @sandbox=nil>
44
+
45
+ headers.expires
46
+ # => #<HTTP::Security::HTTPDate: Tue, 31 Mar 1981 00:00:00 GMT ((2444695j,0s,0n),+0s,2299161j)>
47
+
48
+ headers.pragma
49
+ # => #<HTTP::Security::Headers::Pragma:0x00000002ccc5e8 @no_cache=true>
50
+
51
+ headers.strict_transport_security
52
+ # => #<HTTP::Security::Headers::StrictTransportSecurity:0x00000002c928c0 @max_age=631138519, @include_sub_domains=nil>
53
+
54
+ headers.x_content_type_options
55
+ # => #<HTTP::Security::Headers::XContentTypeOptions:0x00000002a46e40 @no_sniff=true>
56
+
57
+ headers.x_frame_options
58
+ # => #<HTTP::Security::Headers::XFrameOptions:0x000000028163c8 @deny=nil, @same_origin=true, @allow_from=nil, @allow_all=nil>
59
+
60
+ headers.x_permitted_cross_domain_policies
61
+ # => nil
62
+
63
+ headers.x_xss_protection
64
+ # => #<HTTP::Security::Headers::XXSSProtection:0x0000000297a408 @enabled=true, @mode="block"@8, @report=nil>
65
+
66
+ ## Requirements
67
+
68
+ * [ruby] >= 1.9.1
69
+ * [parslet] ~> 1.5
70
+
71
+ ## Install
72
+
73
+ $ gem install http-security
74
+
75
+ ## Testing
76
+
77
+ To run the RSpec tests:
78
+
79
+ $ rake spec
80
+
81
+ To test the parser against the Alexa Top 100:
82
+
83
+ $ rake spec:gauntlet
84
+
85
+ ## License
86
+
87
+ See the {file:LICENSE.txt} file.
88
+
89
+ [ruby]: https://www.ruby-lang.org/
90
+ [parslet]: http://kschiess.github.io/parslet/
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+
5
+ begin
6
+ require 'bundler/setup'
7
+ rescue LoadError => e
8
+ warn e.message
9
+ warn "Run `gem install bundler` to install Bundler."
10
+ exit -1
11
+ end
12
+
13
+ require 'rake'
14
+ require 'rubygems/tasks'
15
+ Gem::Tasks.new
16
+
17
+ require 'rspec/core/rake_task'
18
+ RSpec::Core::RakeTask.new
19
+
20
+ namespace :spec do
21
+ desc "Tests SecurityHeaders::Parser against Alexa Top 500"
22
+ RSpec::Core::RakeTask.new(:gauntlet) do |t|
23
+ t.rspec_opts = '--tag gauntlet'
24
+ end
25
+ end
26
+
27
+ task :test => :spec
28
+ task :default => :spec
29
+
30
+ require 'yard'
31
+ YARD::Rake::YardocTask.new
32
+ task :doc => :yard
33
+
34
+ require_relative 'tasks/alexa'
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'http/security/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "http-security"
8
+ gem.version = HTTP::Security::VERSION
9
+ gem.authors = ["Dominic Owen", "Hal Brodigan"]
10
+ gem.email = ["dwowen20@gmail.com", "hal@trailofbits.com"]
11
+ gem.summary = %q{HTTP Security Header Parser}
12
+ gem.description = %q{HTTP Security Header Parser}
13
+ gem.homepage = "https://github.com/trailofbits/http-security#readme"
14
+ gem.license = "MIT"
15
+
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+ gem.required_ruby_version = '>= 1.9.1'
20
+
21
+ gem.add_dependency 'parslet', '~> 1.5'
22
+ gem.add_development_dependency "bundler", "~> 1.0"
23
+ end
@@ -0,0 +1,2 @@
1
+ require 'http/security/version'
2
+ require 'http/security/response'
@@ -0,0 +1,8 @@
1
+ require 'parslet'
2
+
3
+ module HTTP
4
+ module Security
5
+ class InvalidHeader < Parslet::ParseFailed
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ require 'http/security/headers/cache_control'
2
+ require 'http/security/headers/content_security_policy'
3
+ require 'http/security/headers/content_security_policy_report_only'
4
+ require 'http/security/headers/pragma'
5
+ require 'http/security/headers/public_key_pins'
6
+ require 'http/security/headers/public_key_pins_report_only'
7
+ require 'http/security/headers/set_cookie'
8
+ require 'http/security/headers/strict_transport_security'
9
+ require 'http/security/headers/x_content_type_options'
10
+ require 'http/security/headers/x_frame_options'
11
+ require 'http/security/headers/x_permitted_cross_domain_policies'
12
+ require 'http/security/headers/x_xss_protection'
@@ -0,0 +1,36 @@
1
+ module HTTP
2
+ module Security
3
+ module Headers
4
+ class CacheControl
5
+
6
+ attr_reader :max_age
7
+
8
+ def initialize(options={})
9
+ @private = options[:private]
10
+ @max_age = options[:max_age]
11
+ @no_cache = options[:no_cache]
12
+ end
13
+
14
+ def private?
15
+ !!@private
16
+ end
17
+
18
+ def no_cache?
19
+ !!@no_cache
20
+ end
21
+
22
+ def to_s
23
+ directives = []
24
+
25
+
26
+ directives << "private" if @private
27
+ directives << "max-age=#{@max_age}" if @max_age
28
+ directives << "no-cache" if @no_cache
29
+
30
+ return directives.join(', ')
31
+ end
32
+
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,71 @@
1
+ module HTTP
2
+ module Security
3
+ module Headers
4
+ class ContentSecurityPolicy
5
+
6
+ attr_reader :default_src
7
+
8
+ attr_reader :script_src
9
+
10
+ attr_reader :object_src
11
+
12
+ attr_reader :style_src
13
+
14
+ attr_reader :img_src
15
+
16
+ attr_reader :media_src
17
+
18
+ attr_reader :frame_src
19
+
20
+ attr_reader :font_src
21
+
22
+ attr_reader :connect_src
23
+
24
+ # @return [Array<URI>]
25
+ attr_reader :report_uri
26
+
27
+ attr_reader :sandbox
28
+
29
+ def initialize(directives={})
30
+ @default_src = directives[:default_src]
31
+ @script_src = directives[:script_src]
32
+ @object_src = directives[:object_src]
33
+ @style_src = directives[:style_src]
34
+ @img_src = directives[:img_src]
35
+ @media_src = directives[:media_src]
36
+ @frame_src = directives[:frame_src]
37
+ @font_src = directives[:font_src]
38
+ @connect_src = directives[:connect_src]
39
+
40
+ @report_uri = Array(directives[:report_uri])
41
+ @sandbox = directives[:sandbox]
42
+ end
43
+
44
+ def to_s
45
+ directives = []
46
+
47
+ directives << "default-src #{@default_src}" if @default_src
48
+ directives << "script-src #{@script_src}" if @script_src
49
+ directives << "object-src #{@object_src}" if @object_src
50
+ directives << "style-src #{@style_src}" if @style_src
51
+ directives << "img-src #{@img_src}" if @img_src
52
+ directives << "media-src #{@media_src}" if @media_src
53
+ directives << "frame-src #{@frame_src}" if @frame_src
54
+ directives << "font-src #{@font_src}" if @font_src
55
+ directives << "connect-src #{@connect_src}" if @connect_src
56
+
57
+ if @sandbox
58
+ directives << "sandbox #{@sandbox}"
59
+ end
60
+
61
+ unless @report_uri.empty?
62
+ directives << "report-uri #{@report_uri.join(' ')}"
63
+ end
64
+
65
+ return directives.join('; ')
66
+ end
67
+
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,10 @@
1
+ require 'http/security/headers/content_security_policy'
2
+
3
+ module HTTP
4
+ module Security
5
+ module Headers
6
+ class ContentSecurityPolicyReportOnly < ContentSecurityPolicy
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,24 @@
1
+ module HTTP
2
+ module Security
3
+ module Headers
4
+ class Pragma
5
+
6
+ def initialize(directives={})
7
+ @no_cache = directives[:no_cache]
8
+ end
9
+
10
+ def no_cache?
11
+ !!@no_cache
12
+ end
13
+
14
+ def to_s
15
+ str = ''
16
+ str << 'no-cache' if @no_cache
17
+
18
+ return str
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,60 @@
1
+ module HTTP
2
+ module Security
3
+ module Headers
4
+ class PublicKeyPins
5
+
6
+ # @return [Hash{Symbol,String => Array<String>}]
7
+ attr_reader :pin
8
+
9
+ # @return [Integer]
10
+ attr_reader :max_age
11
+
12
+ # @return [URI::HTTP]
13
+ attr_reader :report_uri
14
+
15
+ def initialize(options={})
16
+ @pin = {}
17
+
18
+ options.each do |key,value|
19
+ if (key.kind_of?(Symbol) && key =~ /^pin_/)
20
+ @pin[key[4..-1].to_sym] = Array(value)
21
+ elsif (key.kind_of?(String) && key.start_with?('pin-'))
22
+ @pin[key[4..-1]] = Array(value)
23
+ end
24
+ end
25
+
26
+ @max_age = options[:max_age]
27
+ @include_sub_domains = options[:includesubdomains]
28
+ @report_uri = options[:report_uri]
29
+ @strict = options[:strict]
30
+ end
31
+
32
+ def include_sub_domains?
33
+ !!@include_sub_domains
34
+ end
35
+
36
+ def strict?
37
+ !!@strict
38
+ end
39
+
40
+ def to_s
41
+ directives = []
42
+
43
+ @pin.each do |algorithm,fingerprints|
44
+ Array(fingerprints).each do |fingerprint|
45
+ directives << "pin-#{algorithm}=#{fingerprint.dump}"
46
+ end
47
+ end
48
+
49
+ directives << "max-age=#{@max_age}" if @max_age
50
+ directives << "includeSubdomains" if @include_sub_domains
51
+ directives << "report-uri=\"#{@report_uri}\"" if @report_uri
52
+ directives << "strict" if @strict
53
+
54
+ return directives.join('; ')
55
+ end
56
+
57
+ end
58
+ end
59
+ end
60
+ end