secure_headers 1.4.1 → 2.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.
Potentially problematic release.
This version of secure_headers might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/.gitignore +4 -8
- data/Gemfile +2 -2
- data/Guardfile +8 -0
- data/README.md +102 -48
- data/Rakefile +0 -116
- data/fixtures/rails_3_2_12/app/views/layouts/application.html.erb +1 -1
- data/fixtures/rails_3_2_12/app/views/other_things/index.html.erb +2 -1
- data/fixtures/rails_3_2_12/config/initializers/secure_headers.rb +1 -1
- data/fixtures/rails_3_2_12/config/script_hashes.yml +5 -0
- data/fixtures/rails_3_2_12/config.ru +3 -0
- data/fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb +50 -18
- data/fixtures/rails_3_2_12/spec/controllers/things_controller_spec.rb +1 -1
- data/fixtures/rails_3_2_12_no_init/app/controllers/other_things_controller.rb +1 -2
- data/lib/secure_headers/hash_helper.rb +7 -0
- data/lib/secure_headers/headers/content_security_policy/script_hash_middleware.rb +22 -0
- data/lib/secure_headers/headers/content_security_policy.rb +141 -137
- data/lib/secure_headers/railtie.rb +0 -22
- data/lib/secure_headers/version.rb +1 -1
- data/lib/secure_headers/view_helper.rb +68 -0
- data/lib/secure_headers.rb +51 -17
- data/lib/tasks/tasks.rake +48 -0
- data/spec/lib/secure_headers/headers/content_security_policy/script_hash_middleware_spec.rb +47 -0
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +83 -208
- data/spec/lib/secure_headers_spec.rb +16 -62
- data/spec/spec_helper.rb +25 -1
- metadata +22 -24
- data/HISTORY.md +0 -162
- data/app/controllers/content_security_policy_controller.rb +0 -76
- data/config/curl-ca-bundle.crt +0 -5420
- data/config/routes.rb +0 -3
- data/spec/controllers/content_security_policy_controller_spec.rb +0 -90
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA1:
         | 
| 3 | 
            +
              metadata.gz: 4d41b253fa38de884f864558563330b9e707e2dc
         | 
| 4 | 
            +
              data.tar.gz: 69b19dbc4ab124481e6f47dad73cd7a6497aa844
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: fa7e778e7cb305a3273d4b3aa94bf8597972910da752b218ebf0779330c4c025c2906cec3974a410193db600fc899465a2da8bc7a8ca5720eb30ba95335e7a5d
         | 
| 7 | 
            +
              data.tar.gz: 21934fb364794d3fc08e7dde201e04cb1253686e362e46ec370b06021007cb897fc3182de9a1ac646fa2448f4ce7a9f45b33da31f4d2a8690b76c42527bb9001
         | 
    
        data/.gitignore
    CHANGED
    
    | @@ -1,4 +1,5 @@ | |
| 1 1 | 
             
            *.gem
         | 
| 2 | 
            +
            *.DS_STORE
         | 
| 2 3 | 
             
            *.rbc
         | 
| 3 4 | 
             
            .bundle
         | 
| 4 5 | 
             
            .config
         | 
| @@ -15,12 +16,7 @@ rdoc | |
| 15 16 | 
             
            spec/reports
         | 
| 16 17 | 
             
            test/tmp
         | 
| 17 18 | 
             
            test/version_tmp
         | 
| 18 | 
            -
            tmp
         | 
| 19 | 
            -
             | 
| 20 | 
            -
            /fixtures/rails_3_2_12/log/test.log
         | 
| 21 | 
            -
            /fixtures/rails_3_2_12_no_init/log/test.log
         | 
| 19 | 
            +
            *tmp
         | 
| 22 20 | 
             
            *.sqlite3
         | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
            /fixtures/rails_3_2_12_no_init/db/test.sqlite3
         | 
| 26 | 
            -
            /fixtures/rails_3_2_12/db/test.sqlite3
         | 
| 21 | 
            +
            fixtures/rails_3_2_12_no_init/log
         | 
| 22 | 
            +
            fixtures/rails_3_2_12/log
         | 
    
        data/Gemfile
    CHANGED
    
    | @@ -8,8 +8,8 @@ group :test do | |
| 8 8 | 
             
              gem 'jdbc-sqlite3', :platform => :jruby
         | 
| 9 9 | 
             
              gem 'rspec-rails', '>= 3.1'
         | 
| 10 10 | 
             
              gem 'rspec', '>= 3.1'
         | 
| 11 | 
            +
              gem 'guard-rspec', :platform => [:ruby_19, :ruby_20, :ruby_21]
         | 
| 11 12 | 
             
              gem 'growl'
         | 
| 12 13 | 
             
              gem 'rb-fsevent'
         | 
| 13 | 
            -
              gem ' | 
| 14 | 
            -
              gem 'ruby-debug', :platform => :ruby_18
         | 
| 14 | 
            +
              gem 'coveralls', :platform => :ruby_19
         | 
| 15 15 | 
             
            end
         | 
    
        data/Guardfile
    ADDED
    
    | @@ -0,0 +1,8 @@ | |
| 1 | 
            +
            notification :growl
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            guard 'rspec', cmd: 'rspec' do
         | 
| 4 | 
            +
              watch(%r{^spec/.+_spec\.rb$})
         | 
| 5 | 
            +
              watch(%r{^lib/(.+)\.rb$})     { |m| "spec/lib/#{m[1]}_spec.rb" }
         | 
| 6 | 
            +
              watch(%r{^app/controllers/(.+)\.rb$})     { |m| "spec/controllers/#{m[1]}_spec.rb" }
         | 
| 7 | 
            +
              watch('spec/spec_helper.rb')  { "spec" }
         | 
| 8 | 
            +
            end
         | 
    
        data/README.md
    CHANGED
    
    | @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            # SecureHeaders [](http://travis-ci.org/twitter/secureheaders) [](https://codeclimate.com/github/twitter/secureheaders)
         | 
| 1 | 
            +
            # SecureHeaders [](http://travis-ci.org/twitter/secureheaders) [](https://codeclimate.com/github/twitter/secureheaders) [](https://coveralls.io/r/twitter/secureheaders)
         | 
| 2 2 |  | 
| 3 3 | 
             
            The gem will automatically apply several headers that are related to security.  This includes:
         | 
| 4 4 | 
             
            - Content Security Policy (CSP) - Helps detect/prevent XSS, mixed-content, and other classes of attack.  [CSP 1.1 Specification](https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html)
         | 
| @@ -54,8 +54,6 @@ The following methods are going to be called, unless they are provided in a `ski | |
| 54 54 | 
             
            This gem makes a few assumptions about how you will use some features.  For example:
         | 
| 55 55 |  | 
| 56 56 | 
             
            * It fills any blank directives with the value in `:default_src`  Getting a default\-src report is pretty useless.  This way, you will always know what type of violation occurred. You can disable this feature by supplying `:disable_fill_missing => true`. This is referred to as the "effective-directive" in the spec, but is not well supported as of Nov 5, 2013.
         | 
| 57 | 
            -
            * Firefox does not support cross\-origin CSP reports.  If we are using Firefox, AND the value for `:report_uri` does not satisfy the same\-origin requirements, we will instead forward to an internal endpoint (`FF_CSP_ENDPOINT`).  This is also the case if `:report_uri` only contains a path, which we assume will be cross host. This endpoint will in turn forward the request to the value in `:forward_endpoint` without restriction. More information can be found in the "Note on Firefox handling of CSP" section.
         | 
| 58 | 
            -
             | 
| 59 57 |  | 
| 60 58 | 
             
            ## Configuration
         | 
| 61 59 |  | 
| @@ -68,9 +66,9 @@ This gem makes a few assumptions about how you will use some features.  For exam | |
| 68 66 | 
             
              config.x_content_type_options = "nosniff"
         | 
| 69 67 | 
             
              config.x_xss_protection = {:value => 1, :mode => 'block'}
         | 
| 70 68 | 
             
              config.csp = {
         | 
| 71 | 
            -
                :default_src => "https | 
| 72 | 
            -
                :frame_src => "https | 
| 73 | 
            -
                :img_src => "https | 
| 69 | 
            +
                :default_src => "https: self",
         | 
| 70 | 
            +
                :frame_src => "https: http:.twimg.com http://itunes.apple.com",
         | 
| 71 | 
            +
                :img_src => "https:",
         | 
| 74 72 | 
             
                :report_uri => '//example.com/uri-directive'
         | 
| 75 73 | 
             
              }
         | 
| 76 74 | 
             
            end
         | 
| @@ -125,11 +123,6 @@ and [Mozilla CSP specification](https://wiki.mozilla.org/Security/CSP/Specificat | |
| 125 123 | 
             
              # Where reports are sent. Use protocol relative URLs if you are posting to the same domain (TLD+1). Use paths if you are posting to the application serving the header
         | 
| 126 124 | 
             
              :report_uri  => '//mysite.example.com',
         | 
| 127 125 |  | 
| 128 | 
            -
              # Send reports that cannot be sent across host here. These requests are sent
         | 
| 129 | 
            -
              # the server, not the browser. If no value is supplied, it will default to
         | 
| 130 | 
            -
              # the value in report_uri. Use this if you cannot use relative protocols mentioned above due to host mismatches.
         | 
| 131 | 
            -
              :forward_endpoint => 'https://internal.mylogaggregator.example.com'
         | 
| 132 | 
            -
             | 
| 133 126 | 
             
              # these directives all take 'none', 'self', or a globbed pattern
         | 
| 134 127 | 
             
              :img_src     => nil,
         | 
| 135 128 | 
             
              :frame_src   => nil,
         | 
| @@ -144,25 +137,12 @@ and [Mozilla CSP specification](https://wiki.mozilla.org/Security/CSP/Specificat | |
| 144 137 | 
             
              # over http, relaxing the policy
         | 
| 145 138 | 
             
              # e.g.
         | 
| 146 139 | 
             
              # :csp => {
         | 
| 147 | 
            -
              #   :img_src => 'https | 
| 148 | 
            -
              #   :http_additions => {:img_src => 'http | 
| 140 | 
            +
              #   :img_src => 'https:',
         | 
| 141 | 
            +
              #   :http_additions => {:img_src => 'http'}
         | 
| 149 142 | 
             
              # }
         | 
| 150 | 
            -
              # would produce the directive: "img-src https | 
| 143 | 
            +
              # would produce the directive: "img-src https: http:;"
         | 
| 151 144 | 
             
              # when over http, ignored for https requests
         | 
| 152 145 | 
             
              :http_additions => {}
         | 
| 153 | 
            -
             | 
| 154 | 
            -
              # If you have enforce => true, you can use the `experiments` block to
         | 
| 155 | 
            -
              # also produce a report-only header. Values in this block override the
         | 
| 156 | 
            -
              # parent config for the report-only, and leave the enforcing header
         | 
| 157 | 
            -
              # unaltered. http_additions work the same way described above, but
         | 
| 158 | 
            -
              # are added to your report-only header as expected.
         | 
| 159 | 
            -
              :experimental => {
         | 
| 160 | 
            -
                :script_src => 'self',
         | 
| 161 | 
            -
                :img_src => 'https://mycdn.example.com',
         | 
| 162 | 
            -
                :http_additions {
         | 
| 163 | 
            -
                  :img_src => 'http://mycdn.example.com'
         | 
| 164 | 
            -
                }
         | 
| 165 | 
            -
              }
         | 
| 166 146 | 
             
            }
         | 
| 167 147 | 
             
            ```
         | 
| 168 148 |  | 
| @@ -172,19 +152,19 @@ and [Mozilla CSP specification](https://wiki.mozilla.org/Security/CSP/Specificat | |
| 172 152 | 
             
            ```ruby
         | 
| 173 153 | 
             
            # most basic example
         | 
| 174 154 | 
             
            :csp => {
         | 
| 175 | 
            -
              :default_src => "https | 
| 155 | 
            +
              :default_src => "https: inline eval",
         | 
| 176 156 | 
             
              :report_uri => '/uri-directive'
         | 
| 177 157 | 
             
            }
         | 
| 178 158 |  | 
| 179 | 
            -
            > "default-src 'unsafe-inline' 'unsafe-eval' https | 
| 159 | 
            +
            > "default-src 'unsafe-inline' 'unsafe-eval' https:; report-uri /uri-directive;"
         | 
| 180 160 |  | 
| 181 161 | 
             
            # turn off inline scripting/eval
         | 
| 182 162 | 
             
            :csp => {
         | 
| 183 | 
            -
              :default_src => 'https | 
| 163 | 
            +
              :default_src => 'https:',
         | 
| 184 164 | 
             
              :report_uri => '/uri-directive'
         | 
| 185 165 | 
             
            }
         | 
| 186 166 |  | 
| 187 | 
            -
            > "default-src  https | 
| 167 | 
            +
            > "default-src  https:; report-uri /uri-directive;"
         | 
| 188 168 |  | 
| 189 169 | 
             
            # Auction site wants to allow images from anywhere, plugin content from a list of trusted media providers (including a content distribution network), and scripts only from its server hosting sanitized JavaScript
         | 
| 190 170 | 
             
            :csp => {
         | 
| @@ -217,9 +197,13 @@ report-uri csp_reports?enforce=true&app_name=twitter | |
| 217 197 |  | 
| 218 198 | 
             
            ### CSP Level 2 features
         | 
| 219 199 |  | 
| 200 | 
            +
            *NOTE: Currently, only erb is supported. Mustache support isn't far off. Hash sources are valid for inline style blocks but are not yet supported by secure_headers.*
         | 
| 201 | 
            +
             | 
| 202 | 
            +
            #### Nonce
         | 
| 203 | 
            +
             | 
| 220 204 | 
             
            script/style-nonce can be used to whitelist inline content. To do this, add "nonce" to your script/style-src configuration, then set the nonce attributes on the various tags.
         | 
| 221 205 |  | 
| 222 | 
            -
             | 
| 206 | 
            +
            Setting a nonce will also set 'unsafe-inline' for browsers that don't support nonces for backwards compatibility. 'unsafe-inline' is ignored if a nonce is present in a directive in compliant browsers.
         | 
| 223 207 |  | 
| 224 208 | 
             
            ```ruby
         | 
| 225 209 | 
             
            :csp => {
         | 
| @@ -243,29 +227,99 @@ script/style-nonce can be used to whitelist inline content. To do this, add "non | |
| 243 227 | 
             
              console.log("won't execute, not whitelisted")
         | 
| 244 228 | 
             
            </script>
         | 
| 245 229 | 
             
            ```
         | 
| 230 | 
            +
            You can use a view helper to automatically add nonces to script tags:
         | 
| 231 | 
            +
            ```erb
         | 
| 232 | 
            +
            <%= nonced_javascript_tag do %>
         | 
| 233 | 
            +
              console.log("nonced!")
         | 
| 234 | 
            +
            <% end %>
         | 
| 235 | 
            +
            <%= nonced_javascript_tag("nonced without a block!") %>
         | 
| 236 | 
            +
            ```
         | 
| 246 237 |  | 
| 247 | 
            -
             | 
| 238 | 
            +
            becomes:
         | 
| 248 239 |  | 
| 249 | 
            -
             | 
| 240 | 
            +
            ```html
         | 
| 241 | 
            +
            <script nonce="/jRAxuLJsDXAxqhNBB7gg7h55KETtDQBXe4ZL+xIXwI=">
         | 
| 242 | 
            +
            console.log("nonced!")
         | 
| 243 | 
            +
            </script>
         | 
| 244 | 
            +
            ```
         | 
| 245 | 
            +
             | 
| 246 | 
            +
            #### Hash
         | 
| 250 247 |  | 
| 251 | 
            -
             | 
| 248 | 
            +
            setting hash source values will also set 'unsafe-inline' for browsers that don't support hash sources for backwards compatibility. 'unsafe-inline' is ignored if a hash is present in a directive in compliant browsers.
         | 
| 252 249 |  | 
| 253 | 
            -
             | 
| 254 | 
            -
            **This is an unauthenticated, unauthorized endpoint. Only do this if your report\-uri is not on the same origin as your application!!!**
         | 
| 250 | 
            +
            Hash source support works by taking the hash value of the contents of an inline script block and adding the hash "fingerprint" to the CSP header.
         | 
| 255 251 |  | 
| 256 | 
            -
             | 
| 252 | 
            +
            If you only have a few hashes, you can hardcode them for the entire app:
         | 
| 257 253 |  | 
| 258 254 | 
             
            ```ruby
         | 
| 259 | 
            -
             | 
| 255 | 
            +
              config.csp = {
         | 
| 256 | 
            +
                :default_src => "https:",
         | 
| 257 | 
            +
                :script_src => 'self'
         | 
| 258 | 
            +
                :script_hashes => ['sha1-abc', 'sha1-qwe']
         | 
| 259 | 
            +
              }
         | 
| 260 260 | 
             
            ```
         | 
| 261 261 |  | 
| 262 | 
            -
             | 
| 262 | 
            +
            The following will work as well, but may not be as clear:
         | 
| 263 | 
            +
             | 
| 264 | 
            +
            ```ruby
         | 
| 265 | 
            +
              config.csp = {
         | 
| 266 | 
            +
                :default_src => "https:",
         | 
| 267 | 
            +
                :script_src => "self 'sha1-qwe'"
         | 
| 268 | 
            +
              }
         | 
| 269 | 
            +
            ```
         | 
| 263 270 |  | 
| 264 | 
            -
            If the  | 
| 271 | 
            +
            If you find you have many hashes or the content of the script tags change frequently, you can apply these hashes in a more intelligent way. This method expects config/script_hashes.yml to contain a map of templates => [hashes]. When the individual templates, layouts, or partials are rendered the hash values for the script tags in those templates will be automatically added to the header. *Currently, only erb layouts are supported.* This requires the use of middleware:
         | 
| 265 272 |  | 
| 266 273 | 
             
            ```ruby
         | 
| 267 | 
            -
             | 
| 274 | 
            +
            # config.ru
         | 
| 275 | 
            +
            require 'secure_headers/headers/content_security_policy/script_hash_middleware'
         | 
| 276 | 
            +
            use ::SecureHeaders::ContentSecurityPolicy::ScriptHashMiddleware
         | 
| 268 277 | 
             
            ```
         | 
| 278 | 
            +
             | 
| 279 | 
            +
            ```ruby
         | 
| 280 | 
            +
              config.csp = {
         | 
| 281 | 
            +
                :default_src => "https:",
         | 
| 282 | 
            +
                :script_src => 'self',
         | 
| 283 | 
            +
                :script_hash_middleware => true
         | 
| 284 | 
            +
              }
         | 
| 285 | 
            +
            ```
         | 
| 286 | 
            +
             | 
| 287 | 
            +
            Hashes are stored in a yaml file with a mapping of Filename => [list of hashes] in config/script_hashes.yml. You can automatically populate this file by running the following rake task:
         | 
| 288 | 
            +
             | 
| 289 | 
            +
            ```$ bundle exec rake secure_headers:generate_hashes```
         | 
| 290 | 
            +
             | 
| 291 | 
            +
            Which will generate something like:
         | 
| 292 | 
            +
             | 
| 293 | 
            +
            ```yaml
         | 
| 294 | 
            +
            # config/script_hashes.yml
         | 
| 295 | 
            +
            app/views/layouts/application.html.erb:
         | 
| 296 | 
            +
            - sha256-l8OLjZqYRnKilpdE0VosRMvhdYArjXT4NZaK2p7QVvs=
         | 
| 297 | 
            +
            app/templates/articles/edit.html.erb:
         | 
| 298 | 
            +
            - sha256-+7mij1/uCwtCQRWrof2NmOln5qX+5WdVwTLMpi8nuoA=
         | 
| 299 | 
            +
            - sha256-Ny4TRIhhFpnYnSeKC274P6bfAz4TOkezLabavIAU4dA=
         | 
| 300 | 
            +
            - sha256-I5e58Gqbu4WpO9dck18QxO7aYOHKrELIi70it4jIPi0=
         | 
| 301 | 
            +
            - sha256-Po4LMynwnAJHxiTp3DQaQ3YDBj3paN/xrDoKl4OyxY4=
         | 
| 302 | 
            +
            ```
         | 
| 303 | 
            +
             | 
| 304 | 
            +
            In this example, if we visit /articles/edit/[id], the above hashes will automatically be added to the CSP header's
         | 
| 305 | 
            +
            script-src value!
         | 
| 306 | 
            +
             | 
| 307 | 
            +
            You can use plain "script" tags or you can use a built-in helper:
         | 
| 308 | 
            +
             | 
| 309 | 
            +
            ```erb
         | 
| 310 | 
            +
            <%= hashed_javascript_tag do %>
         | 
| 311 | 
            +
            console.log("hashed automatically!")
         | 
| 312 | 
            +
            <% end %>
         | 
| 313 | 
            +
            ```
         | 
| 314 | 
            +
             | 
| 315 | 
            +
            By using the helper, hash values will be computed dynamically in development/test environments. If a dynamically computed hash value does not match what is expected to be found in config/script_hashes.yml a warning message will be printed to the console. If you want to raise exceptions instead, use:
         | 
| 316 | 
            +
             | 
| 317 | 
            +
            ```erb
         | 
| 318 | 
            +
            <%= hashed_javascript_tag(raise_error_on_unrecognized_hash = true) do %>
         | 
| 319 | 
            +
            console.log("will raise an exception if not in script_hashes.yml!")
         | 
| 320 | 
            +
            <% end %>
         | 
| 321 | 
            +
            ```
         | 
| 322 | 
            +
             | 
| 269 323 | 
             
            ### Using with Sinatra
         | 
| 270 324 |  | 
| 271 325 | 
             
            Here's an example using SecureHeaders for Sinatra applications:
         | 
| @@ -282,10 +336,10 @@ require 'secure_headers' | |
| 282 336 | 
             
              config.x_content_type_options = "nosniff"
         | 
| 283 337 | 
             
              config.x_xss_protection = {:value => 1, :mode => false}
         | 
| 284 338 | 
             
              config.csp = {
         | 
| 285 | 
            -
                :default_src => "https | 
| 339 | 
            +
                :default_src => "https: inline eval",
         | 
| 286 340 | 
             
                :report_uri => '//example.com/uri-directive',
         | 
| 287 | 
            -
                :img_src => "https | 
| 288 | 
            -
                :frame_src => "https | 
| 341 | 
            +
                :img_src => "https: data:",
         | 
| 342 | 
            +
                :frame_src => "https: http:.twimg.com http://itunes.apple.com"
         | 
| 289 343 | 
             
              }
         | 
| 290 344 | 
             
            end
         | 
| 291 345 |  | 
| @@ -337,10 +391,10 @@ def before_load | |
| 337 391 | 
             
                config.x_content_type_options = "nosniff"
         | 
| 338 392 | 
             
                config.x_xss_protection       = {:value   => '1', :mode => false}
         | 
| 339 393 | 
             
                config.csp                    = {
         | 
| 340 | 
            -
                  :default_src => "https | 
| 394 | 
            +
                  :default_src => "https: inline eval",
         | 
| 341 395 | 
             
                  :report_uri => '//example.com/uri-directive',
         | 
| 342 | 
            -
                  :img_src => "https | 
| 343 | 
            -
                  :frame_src => "https | 
| 396 | 
            +
                  :img_src => "https: data:",
         | 
| 397 | 
            +
                  :frame_src => "https: http:.twimg.com http://itunes.apple.com"
         | 
| 344 398 | 
             
                }
         | 
| 345 399 | 
             
              end
         | 
| 346 400 | 
             
            end
         | 
    
        data/Rakefile
    CHANGED
    
    | @@ -49,119 +49,3 @@ RDoc::Task.new(:rdoc) do |rdoc| | |
| 49 49 | 
             
              rdoc.options << '--line-numbers'
         | 
| 50 50 | 
             
              rdoc.rdoc_files.include('lib/**/*.rb')
         | 
| 51 51 | 
             
            end
         | 
| 52 | 
            -
             | 
| 53 | 
            -
            UPDATE_URI = 'https://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt?raw=1'
         | 
| 54 | 
            -
            CA_FILE = File.expand_path(File.join('..', 'config', 'curl-ca-bundle.crt'), __FILE__)
         | 
| 55 | 
            -
            task :fetch_ca_bundle do
         | 
| 56 | 
            -
              begin
         | 
| 57 | 
            -
                FileUtils.cp CA_FILE, CA_FILE + ".bak"
         | 
| 58 | 
            -
                uri = URI.parse(UPDATE_URI)
         | 
| 59 | 
            -
                http = Net::HTTP.new(uri.host, uri.port)
         | 
| 60 | 
            -
                http.use_ssl = true
         | 
| 61 | 
            -
                http.ca_file = CA_FILE
         | 
| 62 | 
            -
                http.verify_mode = OpenSSL::SSL::VERIFY_PEER
         | 
| 63 | 
            -
                request = Net::HTTP::Get.new(uri.request_uri)
         | 
| 64 | 
            -
             | 
| 65 | 
            -
                ca_file = StringIO.new(http.request(request).body)
         | 
| 66 | 
            -
                File.open(CA_FILE, 'w') do |f|
         | 
| 67 | 
            -
                  f.puts mozilla_license
         | 
| 68 | 
            -
                end
         | 
| 69 | 
            -
             | 
| 70 | 
            -
                while line = ca_file.gets
         | 
| 71 | 
            -
                  next if line =~ /^#/
         | 
| 72 | 
            -
                  next if line =~ /^\s*$/
         | 
| 73 | 
            -
                  line.chomp!
         | 
| 74 | 
            -
             | 
| 75 | 
            -
                  if line =~ /CKA_LABEL/
         | 
| 76 | 
            -
                    label,type,cert_name = line.split(' ',3)
         | 
| 77 | 
            -
                    cert_name.sub!(/^"/, "")
         | 
| 78 | 
            -
                    cert_name.sub!(/"$/, "")
         | 
| 79 | 
            -
                    next
         | 
| 80 | 
            -
                  end
         | 
| 81 | 
            -
                  if line =~ /CKA_VALUE MULTILINE_OCTAL/
         | 
| 82 | 
            -
                    puts "reading cert for #{cert_name}"
         | 
| 83 | 
            -
                    data=''
         | 
| 84 | 
            -
                    while line = ca_file.gets
         | 
| 85 | 
            -
                      break if line =~ /^END/
         | 
| 86 | 
            -
                      line.chomp!
         | 
| 87 | 
            -
                      line.gsub(/\\([0-3][0-7][0-7])/) { data += $1.oct.chr }
         | 
| 88 | 
            -
                    end
         | 
| 89 | 
            -
             | 
| 90 | 
            -
                    open(CA_FILE, "a") do |fp|
         | 
| 91 | 
            -
                      puts "Appending"
         | 
| 92 | 
            -
                      fp.puts cert_name
         | 
| 93 | 
            -
                      fp.puts "================"
         | 
| 94 | 
            -
                      fp.puts "-----BEGIN CERTIFICATE-----"
         | 
| 95 | 
            -
                      fp.puts [data].pack("m*")
         | 
| 96 | 
            -
                      fp.puts "-----END CERTIFICATE-----"
         | 
| 97 | 
            -
                      fp.puts
         | 
| 98 | 
            -
                    end
         | 
| 99 | 
            -
                    puts "Parsing: " + cert_name
         | 
| 100 | 
            -
                  end
         | 
| 101 | 
            -
                end
         | 
| 102 | 
            -
             | 
| 103 | 
            -
                FileUtils.rm CA_FILE + ".bak"
         | 
| 104 | 
            -
              rescue => e
         | 
| 105 | 
            -
                puts "ERRROR #{e}"
         | 
| 106 | 
            -
                puts e.backtrace
         | 
| 107 | 
            -
                FileUtils.mv CA_FILE + '.bak', CA_FILE
         | 
| 108 | 
            -
              end
         | 
| 109 | 
            -
            end
         | 
| 110 | 
            -
             | 
| 111 | 
            -
             | 
| 112 | 
            -
            def mozilla_license
         | 
| 113 | 
            -
            <<-EOM
         | 
| 114 | 
            -
            ##  generated using a modified version of http://curl.haxx.se/mail/lib-2004-07/att-0134/parse-certs.sh
         | 
| 115 | 
            -
            ##
         | 
| 116 | 
            -
            ## lib/ca-bundle.crt -- Bundle of CA Root Certificates
         | 
| 117 | 
            -
            ##
         | 
| 118 | 
            -
            ## Certificate data from Mozilla as of: Tue Mar 27 20:21:58 2012
         | 
| 119 | 
            -
            ##
         | 
| 120 | 
            -
            ## This is a bundle of X.509 certificates of public Certificate Authorities
         | 
| 121 | 
            -
            ## (CA). These were automatically extracted from Mozilla's root certificates
         | 
| 122 | 
            -
            ## file (certdata.txt).  This file can be found in the mozilla source tree:
         | 
| 123 | 
            -
            ## http://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt?raw=1
         | 
| 124 | 
            -
            ##
         | 
| 125 | 
            -
            ## It contains the certificates in PEM format and therefore
         | 
| 126 | 
            -
            ## can be directly used with curl / libcurl / php_curl, or with
         | 
| 127 | 
            -
            ## an Apache+mod_ssl webserver for SSL client authentication.
         | 
| 128 | 
            -
            ## Just configure this file as the SSLCACertificateFile.
         | 
| 129 | 
            -
            ##
         | 
| 130 | 
            -
             | 
| 131 | 
            -
            # ***** BEGIN LICENSE BLOCK *****
         | 
| 132 | 
            -
            # Version: MPL 1.1/GPL 2.0/LGPL 2.1
         | 
| 133 | 
            -
            #
         | 
| 134 | 
            -
            # The contents of this file are subject to the Mozilla Public License Version
         | 
| 135 | 
            -
            # 1.1 (the "License"); you may not use this file except in compliance with
         | 
| 136 | 
            -
            # the License. You may obtain a copy of the License at
         | 
| 137 | 
            -
            # http://www.mozilla.org/MPL/
         | 
| 138 | 
            -
            #
         | 
| 139 | 
            -
            # Software distributed under the License is distributed on an "AS IS" basis,
         | 
| 140 | 
            -
            # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
         | 
| 141 | 
            -
            # for the specific language governing rights and limitations under the
         | 
| 142 | 
            -
            # License.
         | 
| 143 | 
            -
            #
         | 
| 144 | 
            -
            # The Original Code is the Netscape security libraries.
         | 
| 145 | 
            -
            #
         | 
| 146 | 
            -
            # The Initial Developer of the Original Code is
         | 
| 147 | 
            -
            # Netscape Communications Corporation.
         | 
| 148 | 
            -
            # Portions created by the Initial Developer are Copyright (C) 1994-2000
         | 
| 149 | 
            -
            # the Initial Developer. All Rights Reserved.
         | 
| 150 | 
            -
            #
         | 
| 151 | 
            -
            # Contributor(s):
         | 
| 152 | 
            -
            #
         | 
| 153 | 
            -
            # Alternatively, the contents of this file may be used under the terms of
         | 
| 154 | 
            -
            # either the GNU General Public License Version 2 or later (the "GPL"), or
         | 
| 155 | 
            -
            # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
         | 
| 156 | 
            -
            # in which case the provisions of the GPL or the LGPL are applicable instead
         | 
| 157 | 
            -
            # of those above. If you wish to allow use of your version of this file only
         | 
| 158 | 
            -
            # under the terms of either the GPL or the LGPL, and not to allow others to
         | 
| 159 | 
            -
            # use your version of this file under the terms of the MPL, indicate your
         | 
| 160 | 
            -
            # decision by deleting the provisions above and replace them with the notice
         | 
| 161 | 
            -
            # and other provisions required by the GPL or the LGPL. If you do not delete
         | 
| 162 | 
            -
            # the provisions above, a recipient may use your version of this file under
         | 
| 163 | 
            -
            # the terms of any one of the MPL, the GPL or the LGPL.
         | 
| 164 | 
            -
            #
         | 
| 165 | 
            -
            # ***** END LICENSE BLOCK *****
         | 
| 166 | 
            -
            EOM
         | 
| 167 | 
            -
            end
         | 
| @@ -1 +1,2 @@ | |
| 1 | 
            -
            index
         | 
| 1 | 
            +
            index
         | 
| 2 | 
            +
            <script>console.log("oh what")</script>
         | 
| @@ -6,9 +6,9 @@ | |
| 6 6 | 
             
              csp = {
         | 
| 7 7 | 
             
                :default_src => "self",
         | 
| 8 8 | 
             
                :script_src => "self nonce",
         | 
| 9 | 
            -
                :disable_chrome_extension => true,
         | 
| 10 9 | 
             
                :disable_fill_missing => true,
         | 
| 11 10 | 
             
                :report_uri => 'somewhere',
         | 
| 11 | 
            +
                :script_hash_middleware => true,
         | 
| 12 12 | 
             
                :enforce => false # false means warnings only
         | 
| 13 13 | 
             
              }
         | 
| 14 14 |  | 
| @@ -1,45 +1,77 @@ | |
| 1 1 | 
             
            require 'spec_helper'
         | 
| 2 2 |  | 
| 3 | 
            +
            require 'secure_headers/headers/content_security_policy/script_hash_middleware'
         | 
| 4 | 
            +
             | 
| 3 5 | 
             
            describe OtherThingsController, :type => :controller do
         | 
| 6 | 
            +
              include Rack::Test::Methods
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              def app
         | 
| 9 | 
            +
                OtherThingsController.action(:index)
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              def request(opts = {})
         | 
| 13 | 
            +
                options = opts.merge(
         | 
| 14 | 
            +
                  {
         | 
| 15 | 
            +
                    'HTTPS' => 'on',
         | 
| 16 | 
            +
                    'HTTP_USER_AGENT' => "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"
         | 
| 17 | 
            +
                  }
         | 
| 18 | 
            +
                )
         | 
| 19 | 
            +
             | 
| 20 | 
            +
             | 
| 21 | 
            +
                Rack::MockRequest.env_for('/', options)
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
             | 
| 4 25 | 
             
              describe "headers" do
         | 
| 26 | 
            +
                before(:each) do
         | 
| 27 | 
            +
                  _, @env = app.call(request)
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 5 30 | 
             
                it "sets the X-XSS-Protection header" do
         | 
| 6 | 
            -
                  get  | 
| 7 | 
            -
                  expect( | 
| 31 | 
            +
                  get '/'
         | 
| 32 | 
            +
                  expect(@env['X-XSS-Protection']).to eq('1; mode=block')
         | 
| 8 33 | 
             
                end
         | 
| 9 34 |  | 
| 10 35 | 
             
                it "sets the X-Frame-Options header" do
         | 
| 11 | 
            -
                  get  | 
| 12 | 
            -
                  expect( | 
| 36 | 
            +
                  get '/'
         | 
| 37 | 
            +
                  expect(@env['X-Frame-Options']).to eq('SAMEORIGIN')
         | 
| 13 38 | 
             
                end
         | 
| 14 39 |  | 
| 15 40 | 
             
                it "sets the CSP header with a local reference to a nonce" do
         | 
| 16 | 
            -
                   | 
| 17 | 
            -
                   | 
| 18 | 
            -
                  expect( | 
| 19 | 
            -
             | 
| 41 | 
            +
                  middleware = ::SecureHeaders::ContentSecurityPolicy::ScriptHashMiddleware.new(app)
         | 
| 42 | 
            +
                  _, env = middleware.call(request(@env))
         | 
| 43 | 
            +
                  expect(env['Content-Security-Policy-Report-Only']).to match(/script-src[^;]*'nonce-[a-zA-Z0-9\+\/=]{44}'/)
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                it "sets the required hashes to whitelist inline script" do
         | 
| 47 | 
            +
                  middleware = ::SecureHeaders::ContentSecurityPolicy::ScriptHashMiddleware.new(app)
         | 
| 48 | 
            +
                  _, env = middleware.call(request(@env))
         | 
| 49 | 
            +
                  hashes = ['sha256-VjDxT7saxd2FgaUQQTWw/jsTnvonaoCP/ACWDBTpyhU=', 'sha256-ZXAcP8a0y1pPMTJW8pUr43c+XBkgYQBwHOPvXk9mq5A=']
         | 
| 50 | 
            +
                  hashes.each do |hash|
         | 
| 51 | 
            +
                    expect(env['Content-Security-Policy-Report-Only']).to include(hash)
         | 
| 52 | 
            +
                  end
         | 
| 20 53 | 
             
                end
         | 
| 21 54 |  | 
| 22 55 | 
             
                it "sets the Strict-Transport-Security header" do
         | 
| 23 | 
            -
                   | 
| 24 | 
            -
                   | 
| 25 | 
            -
                  expect(response.headers['Strict-Transport-Security']).to eq("max-age=315576000")
         | 
| 56 | 
            +
                  get '/'
         | 
| 57 | 
            +
                  expect(@env['Strict-Transport-Security']).to eq("max-age=315576000")
         | 
| 26 58 | 
             
                end
         | 
| 27 59 |  | 
| 28 60 | 
             
                it "sets the X-Download-Options header" do
         | 
| 29 | 
            -
                  get  | 
| 30 | 
            -
                  expect( | 
| 61 | 
            +
                  get '/'
         | 
| 62 | 
            +
                  expect(@env['X-Download-Options']).to eq('noopen')
         | 
| 31 63 | 
             
                end
         | 
| 32 64 |  | 
| 33 65 | 
             
                it "sets the X-Content-Type-Options header" do
         | 
| 34 | 
            -
                  get  | 
| 35 | 
            -
                  expect( | 
| 66 | 
            +
                  get '/'
         | 
| 67 | 
            +
                  expect(@env['X-Content-Type-Options']).to eq("nosniff")
         | 
| 36 68 | 
             
                end
         | 
| 37 69 |  | 
| 38 70 | 
             
                context "using IE" do
         | 
| 39 71 | 
             
                  it "sets the X-Content-Type-Options header" do
         | 
| 40 | 
            -
                     | 
| 41 | 
            -
                    get  | 
| 42 | 
            -
                    expect( | 
| 72 | 
            +
                    @env['HTTP_USER_AGENT'] = "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"
         | 
| 73 | 
            +
                    get '/'
         | 
| 74 | 
            +
                    expect(@env['X-Content-Type-Options']).to eq("nosniff")
         | 
| 43 75 | 
             
                  end
         | 
| 44 76 | 
             
                end
         | 
| 45 77 | 
             
              end
         | 
| @@ -16,7 +16,7 @@ describe ThingsController, :type => :controller do | |
| 16 16 | 
             
                  expect(response.headers['X-Frame-Options']).to eq('SAMEORIGIN')
         | 
| 17 17 | 
             
                end
         | 
| 18 18 |  | 
| 19 | 
            -
                it " | 
| 19 | 
            +
                it "does not set CSP header" do
         | 
| 20 20 | 
             
                  get :index
         | 
| 21 21 | 
             
                  expect(response.headers['Content-Security-Policy-Report-Only']).to eq(nil)
         | 
| 22 22 | 
             
                end
         | 
| @@ -1,6 +1,5 @@ | |
| 1 1 | 
             
            class OtherThingsController < ApplicationController
         | 
| 2 | 
            -
              ensure_security_headers :csp => {:default_src => 'self', : | 
| 3 | 
            -
                :disable_fill_missing => true}
         | 
| 2 | 
            +
              ensure_security_headers :csp => {:default_src => 'self', :disable_fill_missing => true}
         | 
| 4 3 | 
             
              def index
         | 
| 5 4 |  | 
| 6 5 | 
             
              end
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            module SecureHeaders
         | 
| 2 | 
            +
              class ContentSecurityPolicy
         | 
| 3 | 
            +
                class ScriptHashMiddleware
         | 
| 4 | 
            +
                  def initialize(app)
         | 
| 5 | 
            +
                    @app = app
         | 
| 6 | 
            +
                  end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def call(env)
         | 
| 9 | 
            +
                    status, headers, response = @app.call(env)
         | 
| 10 | 
            +
                    metadata = env[ContentSecurityPolicy::ENV_KEY]
         | 
| 11 | 
            +
                    if !metadata.nil?
         | 
| 12 | 
            +
                      config, options = metadata.values_at(:config, :options)
         | 
| 13 | 
            +
                      config.merge!(:script_hashes => env[HASHES_ENV_KEY])
         | 
| 14 | 
            +
                      csp_header = ContentSecurityPolicy.new(config, options)
         | 
| 15 | 
            +
                      headers[csp_header.name] = csp_header.value
         | 
| 16 | 
            +
                    end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    [status, headers, response]
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         |