rack-protection 2.0.8.1 → 2.2.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.
- checksums.yaml +4 -4
- data/lib/rack/protection/authenticity_token.rb +75 -20
- data/lib/rack/protection/content_security_policy.rb +4 -5
- data/lib/rack/protection/http_origin.rb +11 -4
- data/lib/rack/protection/referrer_policy.rb +25 -0
- data/lib/rack/protection/version.rb +1 -1
- data/lib/rack/protection.rb +4 -1
- metadata +4 -4
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 5ed141bb1184b6760816e1768b5d91246d91a01ef56cb17a163f5370df604026
         | 
| 4 | 
            +
              data.tar.gz: f3ac32c5d25123bb9f5843a47ca17dc796a9e3469a997f32b772ab72200cff58
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 9d78cedbd03ffe9dba1c6bca4e4b3cf2b165b12e9befb1136bda35651e99b88ecd575a44e3f394aee8e0191f7080706ebc1a2462dc5504b19d41a3b7bb47e80b
         | 
| 7 | 
            +
              data.tar.gz: 00466e987ae67ebd7b76be1099a99cba4852987b88dbbccf4f230b1d3ee8dfa40f627cc0463aec39d64788566e08cb71117a5c33688edbb30540949436245f56
         | 
| @@ -1,5 +1,6 @@ | |
| 1 1 | 
             
            require 'rack/protection'
         | 
| 2 2 | 
             
            require 'securerandom'
         | 
| 3 | 
            +
            require 'openssl'
         | 
| 3 4 | 
             
            require 'base64'
         | 
| 4 5 |  | 
| 5 6 | 
             
            module Rack
         | 
| @@ -24,6 +25,13 @@ module Rack | |
| 24 25 | 
             
                #                                the token on a request. Default value:
         | 
| 25 26 | 
             
                #                                <tt>"authenticity_token"</tt>
         | 
| 26 27 | 
             
                #
         | 
| 28 | 
            +
                # [<tt>:key</tt>] the name of the param that should contain
         | 
| 29 | 
            +
                #                                the token in the session. Default value:
         | 
| 30 | 
            +
                #                                <tt>:csrf</tt>
         | 
| 31 | 
            +
                #
         | 
| 32 | 
            +
                # [<tt>:allow_if</tt>] a proc for custom allow/deny logic. Default value:
         | 
| 33 | 
            +
                #                                <tt>nil</tt>
         | 
| 34 | 
            +
                #
         | 
| 27 35 | 
             
                # == Example: Forms application
         | 
| 28 36 | 
             
                #
         | 
| 29 37 | 
             
                # To show what the AuthenticityToken does, this section includes a sample
         | 
| @@ -63,7 +71,7 @@ module Rack | |
| 63 71 | 
             
                #             <h1>With Authenticity Token</h1>
         | 
| 64 72 | 
             
                #             <p>This successfully takes you to back to this form.</p>
         | 
| 65 73 | 
             
                #             <form action="" method="post">
         | 
| 66 | 
            -
                #               <input type="hidden" name="authenticity_token" value="#{env['rack.session'] | 
| 74 | 
            +
                #               <input type="hidden" name="authenticity_token" value="#{Rack::Protection::AuthenticityToken.token(env['rack.session'])}" />
         | 
| 67 75 | 
             
                #               <input type="text" name="foo" />
         | 
| 68 76 | 
             
                #               <input type="submit" />
         | 
| 69 77 | 
             
                #             </form>
         | 
| @@ -85,42 +93,57 @@ module Rack | |
| 85 93 | 
             
                  TOKEN_LENGTH = 32
         | 
| 86 94 |  | 
| 87 95 | 
             
                  default_options :authenticity_param => 'authenticity_token',
         | 
| 96 | 
            +
                                  :key => :csrf,
         | 
| 88 97 | 
             
                                  :allow_if => nil
         | 
| 89 98 |  | 
| 90 | 
            -
                  def self.token(session)
         | 
| 91 | 
            -
                    self.new(nil).mask_authenticity_token(session)
         | 
| 99 | 
            +
                  def self.token(session, path: nil, method: :post)
         | 
| 100 | 
            +
                    self.new(nil).mask_authenticity_token(session, path: path, method: method)
         | 
| 92 101 | 
             
                  end
         | 
| 93 102 |  | 
| 94 103 | 
             
                  def self.random_token
         | 
| 95 | 
            -
                    SecureRandom. | 
| 104 | 
            +
                    SecureRandom.urlsafe_base64(TOKEN_LENGTH, padding: false)
         | 
| 96 105 | 
             
                  end
         | 
| 97 106 |  | 
| 98 107 | 
             
                  def accepts?(env)
         | 
| 99 | 
            -
                    session = session | 
| 108 | 
            +
                    session = session(env)
         | 
| 100 109 | 
             
                    set_token(session)
         | 
| 101 110 |  | 
| 102 111 | 
             
                    safe?(env) ||
         | 
| 103 | 
            -
                      valid_token?( | 
| 104 | 
            -
                      valid_token?( | 
| 112 | 
            +
                      valid_token?(env, env['HTTP_X_CSRF_TOKEN']) ||
         | 
| 113 | 
            +
                      valid_token?(env, Request.new(env).params[options[:authenticity_param]]) ||
         | 
| 105 114 | 
             
                      ( options[:allow_if] && options[:allow_if].call(env) )
         | 
| 115 | 
            +
                  rescue
         | 
| 116 | 
            +
                    false
         | 
| 106 117 | 
             
                  end
         | 
| 107 118 |  | 
| 108 | 
            -
                  def mask_authenticity_token(session)
         | 
| 109 | 
            -
                     | 
| 119 | 
            +
                  def mask_authenticity_token(session, path: nil, method: :post)
         | 
| 120 | 
            +
                    set_token(session)
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                    token = if path && method
         | 
| 123 | 
            +
                      per_form_token(session, path, method)
         | 
| 124 | 
            +
                    else
         | 
| 125 | 
            +
                      global_token(session)
         | 
| 126 | 
            +
                    end
         | 
| 127 | 
            +
             | 
| 110 128 | 
             
                    mask_token(token)
         | 
| 111 129 | 
             
                  end
         | 
| 112 130 |  | 
| 131 | 
            +
                  GLOBAL_TOKEN_IDENTIFIER = '!real_csrf_token'
         | 
| 132 | 
            +
                  private_constant :GLOBAL_TOKEN_IDENTIFIER
         | 
| 133 | 
            +
             | 
| 113 134 | 
             
                  private
         | 
| 114 135 |  | 
| 115 136 | 
             
                  def set_token(session)
         | 
| 116 | 
            -
                    session[: | 
| 137 | 
            +
                    session[options[:key]] ||= self.class.random_token
         | 
| 117 138 | 
             
                  end
         | 
| 118 139 |  | 
| 119 140 | 
             
                  # Checks the client's masked token to see if it matches the
         | 
| 120 141 | 
             
                  # session token.
         | 
| 121 | 
            -
                  def valid_token?( | 
| 142 | 
            +
                  def valid_token?(env, token)
         | 
| 122 143 | 
             
                    return false if token.nil? || token.empty?
         | 
| 123 144 |  | 
| 145 | 
            +
                    session = session(env)
         | 
| 146 | 
            +
             | 
| 124 147 | 
             
                    begin
         | 
| 125 148 | 
             
                      token = decode_token(token)
         | 
| 126 149 | 
             
                    rescue ArgumentError # encoded_masked_token is invalid Base64
         | 
| @@ -131,13 +154,13 @@ module Rack | |
| 131 154 | 
             
                    # to handle any unmasked tokens that we've issued without error.
         | 
| 132 155 |  | 
| 133 156 | 
             
                    if unmasked_token?(token)
         | 
| 134 | 
            -
                      compare_with_real_token | 
| 135 | 
            -
             | 
| 157 | 
            +
                      compare_with_real_token(token, session)
         | 
| 136 158 | 
             
                    elsif masked_token?(token)
         | 
| 137 159 | 
             
                      token = unmask_token(token)
         | 
| 138 160 |  | 
| 139 | 
            -
                       | 
| 140 | 
            -
             | 
| 161 | 
            +
                      compare_with_global_token(token, session) ||
         | 
| 162 | 
            +
                        compare_with_real_token(token, session) ||
         | 
| 163 | 
            +
                        compare_with_per_form_token(token, session, Request.new(env))
         | 
| 141 164 | 
             
                    else
         | 
| 142 165 | 
             
                      false # Token is malformed
         | 
| 143 166 | 
             
                    end
         | 
| @@ -147,7 +170,6 @@ module Rack | |
| 147 170 | 
             
                  # on each request. The masking is used to mitigate SSL attacks
         | 
| 148 171 | 
             
                  # like BREACH.
         | 
| 149 172 | 
             
                  def mask_token(token)
         | 
| 150 | 
            -
                    token = decode_token(token)
         | 
| 151 173 | 
             
                    one_time_pad = SecureRandom.random_bytes(token.length)
         | 
| 152 174 | 
             
                    encrypted_token = xor_byte_strings(one_time_pad, token)
         | 
| 153 175 | 
             
                    masked_token = one_time_pad + encrypted_token
         | 
| @@ -176,20 +198,53 @@ module Rack | |
| 176 198 | 
             
                    secure_compare(token, real_token(session))
         | 
| 177 199 | 
             
                  end
         | 
| 178 200 |  | 
| 201 | 
            +
                  def compare_with_global_token(token, session)
         | 
| 202 | 
            +
                    secure_compare(token, global_token(session))
         | 
| 203 | 
            +
                  end
         | 
| 204 | 
            +
             | 
| 205 | 
            +
                  def compare_with_per_form_token(token, session, request)
         | 
| 206 | 
            +
                    secure_compare(token,
         | 
| 207 | 
            +
                      per_form_token(session, request.path.chomp('/'), request.request_method)
         | 
| 208 | 
            +
                    )
         | 
| 209 | 
            +
                  end
         | 
| 210 | 
            +
             | 
| 179 211 | 
             
                  def real_token(session)
         | 
| 180 | 
            -
                    decode_token(session[: | 
| 212 | 
            +
                    decode_token(session[options[:key]])
         | 
| 213 | 
            +
                  end
         | 
| 214 | 
            +
             | 
| 215 | 
            +
                  def global_token(session)
         | 
| 216 | 
            +
                    token_hmac(session, GLOBAL_TOKEN_IDENTIFIER)
         | 
| 217 | 
            +
                  end
         | 
| 218 | 
            +
             | 
| 219 | 
            +
                  def per_form_token(session, path, method)
         | 
| 220 | 
            +
                    token_hmac(session, "#{path}##{method.downcase}")
         | 
| 181 221 | 
             
                  end
         | 
| 182 222 |  | 
| 183 223 | 
             
                  def encode_token(token)
         | 
| 184 | 
            -
                    Base64. | 
| 224 | 
            +
                    Base64.urlsafe_encode64(token)
         | 
| 185 225 | 
             
                  end
         | 
| 186 226 |  | 
| 187 227 | 
             
                  def decode_token(token)
         | 
| 188 | 
            -
                    Base64. | 
| 228 | 
            +
                    Base64.urlsafe_decode64(token)
         | 
| 229 | 
            +
                  end
         | 
| 230 | 
            +
             | 
| 231 | 
            +
                  def token_hmac(session, identifier)
         | 
| 232 | 
            +
                    OpenSSL::HMAC.digest(
         | 
| 233 | 
            +
                      OpenSSL::Digest::SHA256.new,
         | 
| 234 | 
            +
                      real_token(session),
         | 
| 235 | 
            +
                      identifier
         | 
| 236 | 
            +
                    )
         | 
| 189 237 | 
             
                  end
         | 
| 190 238 |  | 
| 191 239 | 
             
                  def xor_byte_strings(s1, s2)
         | 
| 192 | 
            -
                     | 
| 240 | 
            +
                    s2 = s2.dup
         | 
| 241 | 
            +
                    size = s1.bytesize
         | 
| 242 | 
            +
                    i = 0
         | 
| 243 | 
            +
                    while i < size
         | 
| 244 | 
            +
                      s2.setbyte(i, s1.getbyte(i) ^ s2.getbyte(i))
         | 
| 245 | 
            +
                      i += 1
         | 
| 246 | 
            +
                    end
         | 
| 247 | 
            +
                    s2
         | 
| 193 248 | 
             
                  end
         | 
| 194 249 | 
             
                end
         | 
| 195 250 | 
             
              end
         | 
| @@ -36,16 +36,15 @@ module Rack | |
| 36 36 | 
             
                #          to be used in a policy.
         | 
| 37 37 | 
             
                #
         | 
| 38 38 | 
             
                class ContentSecurityPolicy < Base
         | 
| 39 | 
            -
                  default_options default_src:  | 
| 40 | 
            -
                                  img_src: "'self'", style_src: "'self'",
         | 
| 41 | 
            -
                                  connect_src: "'self'", report_only: false
         | 
| 39 | 
            +
                  default_options default_src: "'self'", report_only: false
         | 
| 42 40 |  | 
| 43 41 | 
             
                  DIRECTIVES = %i(base_uri child_src connect_src default_src
         | 
| 44 42 | 
             
                                  font_src form_action frame_ancestors frame_src
         | 
| 45 43 | 
             
                                  img_src manifest_src media_src object_src
         | 
| 46 44 | 
             
                                  plugin_types referrer reflected_xss report_to
         | 
| 47 45 | 
             
                                  report_uri require_sri_for sandbox script_src
         | 
| 48 | 
            -
                                  style_src worker_src | 
| 46 | 
            +
                                  style_src worker_src webrtc_src navigate_to
         | 
| 47 | 
            +
                                  prefetch_src).freeze
         | 
| 49 48 |  | 
| 50 49 | 
             
                  NO_ARG_DIRECTIVES = %i(block_all_mixed_content disown_opener
         | 
| 51 50 | 
             
                                         upgrade_insecure_requests).freeze
         | 
| @@ -62,7 +61,7 @@ module Rack | |
| 62 61 | 
             
                    # Set these key values to boolean 'true' to include in policy
         | 
| 63 62 | 
             
                    NO_ARG_DIRECTIVES.each do |d|
         | 
| 64 63 | 
             
                      if options.key?(d) && options[d].is_a?(TrueClass)
         | 
| 65 | 
            -
                        directives << d.to_s. | 
| 64 | 
            +
                        directives << d.to_s.tr('_', '-')
         | 
| 66 65 | 
             
                      end
         | 
| 67 66 | 
             
                    end
         | 
| 68 67 |  | 
| @@ -9,11 +9,11 @@ module Rack | |
| 9 9 | 
             
                #                      http://tools.ietf.org/html/draft-abarth-origin
         | 
| 10 10 | 
             
                #
         | 
| 11 11 | 
             
                # Does not accept unsafe HTTP requests when value of Origin HTTP request header
         | 
| 12 | 
            -
                # does not match default or  | 
| 12 | 
            +
                # does not match default or permitted URIs.
         | 
| 13 13 | 
             
                #
         | 
| 14 | 
            -
                # If you want to  | 
| 14 | 
            +
                # If you want to permit a specific domain, you can pass in as the `:permitted_origins` option:
         | 
| 15 15 | 
             
                #
         | 
| 16 | 
            -
                #     use Rack::Protection,  | 
| 16 | 
            +
                #     use Rack::Protection, permitted_origins: ["http://localhost:3000", "http://127.0.01:3000"]
         | 
| 17 17 | 
             
                #
         | 
| 18 18 | 
             
                # The `:allow_if` option can also be set to a proc to use custom allow/deny logic.
         | 
| 19 19 | 
             
                class HttpOrigin < Base
         | 
| @@ -32,7 +32,14 @@ module Rack | |
| 32 32 | 
             
                    return true unless origin = env['HTTP_ORIGIN']
         | 
| 33 33 | 
             
                    return true if base_url(env) == origin
         | 
| 34 34 | 
             
                    return true if options[:allow_if] && options[:allow_if].call(env)
         | 
| 35 | 
            -
             | 
| 35 | 
            +
             | 
| 36 | 
            +
                    if options.key? :origin_whitelist
         | 
| 37 | 
            +
                      warn env, "Rack::Protection origin_whitelist option is deprecated and will be removed, " \
         | 
| 38 | 
            +
                        "use permitted_origins instead.\n"
         | 
| 39 | 
            +
                    end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    permitted_origins = options[:permitted_origins] || options[:origin_whitelist]
         | 
| 42 | 
            +
                    Array(permitted_origins).include? origin
         | 
| 36 43 | 
             
                  end
         | 
| 37 44 |  | 
| 38 45 | 
             
                end
         | 
| @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            require 'rack/protection'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Rack
         | 
| 4 | 
            +
              module Protection
         | 
| 5 | 
            +
                ##
         | 
| 6 | 
            +
                # Prevented attack::   Secret leakage, third party tracking
         | 
| 7 | 
            +
                # Supported browsers:: mixed support
         | 
| 8 | 
            +
                # More infos::         https://www.w3.org/TR/referrer-policy/
         | 
| 9 | 
            +
                #                      https://caniuse.com/#search=referrer-policy
         | 
| 10 | 
            +
                #
         | 
| 11 | 
            +
                # Sets Referrer-Policy header to tell the browser to limit the Referer header.
         | 
| 12 | 
            +
                #
         | 
| 13 | 
            +
                # Options:
         | 
| 14 | 
            +
                # referrer_policy:: The policy to use (default: 'strict-origin-when-cross-origin')
         | 
| 15 | 
            +
                class ReferrerPolicy < Base
         | 
| 16 | 
            +
                  default_options :referrer_policy => 'strict-origin-when-cross-origin'
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def call(env)
         | 
| 19 | 
            +
                    status, headers, body = @app.call(env)
         | 
| 20 | 
            +
                    headers['Referrer-Policy'] ||= options[:referrer_policy]
         | 
| 21 | 
            +
                    [status, headers, body]
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
            end
         | 
    
        data/lib/rack/protection.rb
    CHANGED
    
    | @@ -14,6 +14,7 @@ module Rack | |
| 14 14 | 
             
                autoload :IPSpoofing,            'rack/protection/ip_spoofing'
         | 
| 15 15 | 
             
                autoload :JsonCsrf,              'rack/protection/json_csrf'
         | 
| 16 16 | 
             
                autoload :PathTraversal,         'rack/protection/path_traversal'
         | 
| 17 | 
            +
                autoload :ReferrerPolicy,        'rack/protection/referrer_policy'
         | 
| 17 18 | 
             
                autoload :RemoteReferrer,        'rack/protection/remote_referrer'
         | 
| 18 19 | 
             
                autoload :RemoteToken,           'rack/protection/remote_token'
         | 
| 19 20 | 
             
                autoload :SessionHijacking,      'rack/protection/session_hijacking'
         | 
| @@ -32,9 +33,11 @@ module Rack | |
| 32 33 | 
             
                  Rack::Builder.new do
         | 
| 33 34 | 
             
                    # Off by default, unless added
         | 
| 34 35 | 
             
                    use ::Rack::Protection::AuthenticityToken,     options if use_these.include? :authenticity_token
         | 
| 35 | 
            -
                    use ::Rack::Protection::CookieTossing,         options if use_these.include? :cookie_tossing
         | 
| 36 36 | 
             
                    use ::Rack::Protection::ContentSecurityPolicy, options if use_these.include? :content_security_policy
         | 
| 37 | 
            +
                    use ::Rack::Protection::CookieTossing,         options if use_these.include? :cookie_tossing
         | 
| 38 | 
            +
                    use ::Rack::Protection::EscapedParams,         options if use_these.include? :escaped_params
         | 
| 37 39 | 
             
                    use ::Rack::Protection::FormToken,             options if use_these.include? :form_token
         | 
| 40 | 
            +
                    use ::Rack::Protection::ReferrerPolicy,        options if use_these.include? :referrer_policy
         | 
| 38 41 | 
             
                    use ::Rack::Protection::RemoteReferrer,        options if use_these.include? :remote_referrer
         | 
| 39 42 | 
             
                    use ::Rack::Protection::StrictTransport,       options if use_these.include? :strict_transport
         | 
| 40 43 |  | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: rack-protection
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 2.0 | 
| 4 | 
            +
              version: 2.2.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - https://github.com/sinatra/sinatra/graphs/contributors
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date:  | 
| 11 | 
            +
            date: 2022-02-15 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: rack
         | 
| @@ -76,6 +76,7 @@ files: | |
| 76 76 | 
             
            - lib/rack/protection/ip_spoofing.rb
         | 
| 77 77 | 
             
            - lib/rack/protection/json_csrf.rb
         | 
| 78 78 | 
             
            - lib/rack/protection/path_traversal.rb
         | 
| 79 | 
            +
            - lib/rack/protection/referrer_policy.rb
         | 
| 79 80 | 
             
            - lib/rack/protection/remote_referrer.rb
         | 
| 80 81 | 
             
            - lib/rack/protection/remote_token.rb
         | 
| 81 82 | 
             
            - lib/rack/protection/session_hijacking.rb
         | 
| @@ -105,8 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 105 106 | 
             
                - !ruby/object:Gem::Version
         | 
| 106 107 | 
             
                  version: '0'
         | 
| 107 108 | 
             
            requirements: []
         | 
| 108 | 
            -
             | 
| 109 | 
            -
            rubygems_version: 2.7.3
         | 
| 109 | 
            +
            rubygems_version: 3.1.2
         | 
| 110 110 | 
             
            signing_key: 
         | 
| 111 111 | 
             
            specification_version: 4
         | 
| 112 112 | 
             
            summary: Protect against typical web attacks, works with all Rack apps, including
         |