brakeman-lib 4.10.0 → 5.0.0.pre1
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/CHANGES.md +10 -0
- data/README.md +1 -1
- data/lib/brakeman.rb +6 -0
- data/lib/brakeman/app_tree.rb +36 -3
- data/lib/brakeman/checks/check_unsafe_reflection_methods.rb +68 -0
- data/lib/brakeman/checks/check_verb_confusion.rb +75 -0
- data/lib/brakeman/file_parser.rb +19 -18
- data/lib/brakeman/options.rb +5 -1
- data/lib/brakeman/parsers/template_parser.rb +2 -3
- data/lib/brakeman/processors/haml_template_processor.rb +8 -1
- data/lib/brakeman/processors/lib/file_type_detector.rb +64 -0
- data/lib/brakeman/report.rb +8 -0
- data/lib/brakeman/report/report_sonar.rb +38 -0
- data/lib/brakeman/rescanner.rb +7 -5
- data/lib/brakeman/scanner.rb +42 -18
- data/lib/brakeman/tracker.rb +6 -0
- data/lib/brakeman/util.rb +7 -2
- data/lib/brakeman/version.rb +1 -1
- data/lib/brakeman/warning_codes.rb +2 -0
- metadata +11 -7
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: d69b909ed56306516d662fbc2bd25c22c3886925a8a98ca81ea761f0bb3851ca
         | 
| 4 | 
            +
              data.tar.gz: bb2f1fe108cccdefb21da6cce220489d0890659bfaf2a773e3e9201892db0f9b
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 004d9116ce3b94abe715744571e124b477b9e30acaa623e3e46dc28493e9829d9c7cee9a060024eb6aa8158336d0622e81fb68a43343e5a4dddf837c214e62c8
         | 
| 7 | 
            +
              data.tar.gz: af6556b75109436f49a1087ace59aab6c235a82452d92159770fbd8248c56aa458d254307046e73b649563d7471c88a31b049257ad5305498963d815aa1f2439
         | 
    
        data/CHANGES.md
    CHANGED
    
    | @@ -1,3 +1,13 @@ | |
| 1 | 
            +
            # 5.0.0.pre1 - 2020-11-17
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            * Add check for (more) unsafe method reflection
         | 
| 4 | 
            +
            * Suggest using `--force` if no Rails application is detected
         | 
| 5 | 
            +
            * Add Sonarqube report format (Adam England)
         | 
| 6 | 
            +
            * Add check for potential HTTP verb confusion
         | 
| 7 | 
            +
            * Add `--[no-]skip-vendor` option
         | 
| 8 | 
            +
            * Scan (almost) all Ruby files in project
         | 
| 9 | 
            +
            * Add support for Haml 5.2.0
         | 
| 10 | 
            +
             | 
| 1 11 | 
             
            # 4.10.0 - 2020-09-28
         | 
| 2 12 |  | 
| 3 13 | 
             
            * Add SARIF report format (Steve Winton)
         | 
    
        data/README.md
    CHANGED
    
    | @@ -76,7 +76,7 @@ To specify an output file for the results: | |
| 76 76 |  | 
| 77 77 | 
             
                brakeman -o output_file
         | 
| 78 78 |  | 
| 79 | 
            -
            The output format is determined by the file extension or by using the `-f` option. Current options are: `text`, `html`, `tabs`, `json`, `junit`, `markdown`, `csv`, and ` | 
| 79 | 
            +
            The output format is determined by the file extension or by using the `-f` option. Current options are: `text`, `html`, `tabs`, `json`, `junit`, `markdown`, `csv`, `codeclimate`, and `sonar`.
         | 
| 80 80 |  | 
| 81 81 | 
             
            Multiple output files can be specified:
         | 
| 82 82 |  | 
    
        data/lib/brakeman.rb
    CHANGED
    
    | @@ -66,6 +66,7 @@ module Brakeman | |
| 66 66 | 
             
              #  * :run_checks - array of checks to run (run all if not specified)
         | 
| 67 67 | 
             
              #  * :safe_methods - array of methods to consider safe
         | 
| 68 68 | 
             
              #  * :skip_libs - do not process lib/ directory (default: false)
         | 
| 69 | 
            +
              #  * :skip_vendor - do not process vendor/ directory (default: true)
         | 
| 69 70 | 
             
              #  * :skip_checks - checks not to run (run all if not specified)
         | 
| 70 71 | 
             
              #  * :absolute_paths - show absolute path of each file (default: false)
         | 
| 71 72 | 
             
              #  * :summary_only - only output summary section of report for plain/table (:summary_only, :no_summary, true)
         | 
| @@ -191,6 +192,7 @@ module Brakeman | |
| 191 192 | 
             
                  :report_progress => true,
         | 
| 192 193 | 
             
                  :safe_methods => Set.new,
         | 
| 193 194 | 
             
                  :skip_checks => Set.new,
         | 
| 195 | 
            +
                  :skip_vendor => true,
         | 
| 194 196 | 
             
                }
         | 
| 195 197 | 
             
              end
         | 
| 196 198 |  | 
| @@ -239,6 +241,8 @@ module Brakeman | |
| 239 241 | 
             
                  [:to_junit]
         | 
| 240 242 | 
             
                when :sarif, :to_sarif
         | 
| 241 243 | 
             
                  [:to_sarif]
         | 
| 244 | 
            +
                when :sonar, :to_sonar
         | 
| 245 | 
            +
                  [:to_sonar]
         | 
| 242 246 | 
             
                else
         | 
| 243 247 | 
             
                  [:to_text]
         | 
| 244 248 | 
             
                end
         | 
| @@ -270,6 +274,8 @@ module Brakeman | |
| 270 274 | 
             
                    :to_junit
         | 
| 271 275 | 
             
                  when /\.sarif$/i
         | 
| 272 276 | 
             
                    :to_sarif
         | 
| 277 | 
            +
                  when /\.sonar$/i
         | 
| 278 | 
            +
                    :to_sonar
         | 
| 273 279 | 
             
                  else
         | 
| 274 280 | 
             
                    :to_text
         | 
| 275 281 | 
             
                  end
         | 
    
        data/lib/brakeman/app_tree.rb
    CHANGED
    
    | @@ -21,6 +21,7 @@ module Brakeman | |
| 21 21 | 
             
                  end
         | 
| 22 22 | 
             
                  init_options[:additional_libs_path] = options[:additional_libs_path]
         | 
| 23 23 | 
             
                  init_options[:engine_paths] = options[:engine_paths]
         | 
| 24 | 
            +
                  init_options[:skip_vendor] = options[:skip_vendor]
         | 
| 24 25 | 
             
                  new(root, init_options)
         | 
| 25 26 | 
             
                end
         | 
| 26 27 |  | 
| @@ -62,6 +63,7 @@ module Brakeman | |
| 62 63 | 
             
                  @engine_paths = init_options[:engine_paths] || []
         | 
| 63 64 | 
             
                  @absolute_engine_paths = @engine_paths.select { |path| path.start_with?(File::SEPARATOR) }
         | 
| 64 65 | 
             
                  @relative_engine_paths = @engine_paths - @absolute_engine_paths
         | 
| 66 | 
            +
                  @skip_vendor = init_options[:skip_vendor]
         | 
| 65 67 | 
             
                  @gemspec = nil
         | 
| 66 68 | 
             
                  @root_search_pattern = nil
         | 
| 67 69 | 
             
                end
         | 
| @@ -96,6 +98,10 @@ module Brakeman | |
| 96 98 | 
             
                  end
         | 
| 97 99 | 
             
                end
         | 
| 98 100 |  | 
| 101 | 
            +
                def ruby_file_paths
         | 
| 102 | 
            +
                  find_paths(".").uniq
         | 
| 103 | 
            +
                end
         | 
| 104 | 
            +
             | 
| 99 105 | 
             
                def initializer_paths
         | 
| 100 106 | 
             
                  @initializer_paths ||= prioritize_concerns(find_paths("config/initializers"))
         | 
| 101 107 | 
             
                end
         | 
| @@ -109,8 +115,8 @@ module Brakeman | |
| 109 115 | 
             
                end
         | 
| 110 116 |  | 
| 111 117 | 
             
                def template_paths
         | 
| 112 | 
            -
                  @template_paths ||= find_paths(" | 
| 113 | 
            -
             | 
| 118 | 
            +
                  @template_paths ||= find_paths(".", "*.{#{VIEW_EXTENSIONS}}") +
         | 
| 119 | 
            +
                    find_paths("**", "*.{erb,haml,slim}").reject { |path| File.basename(path).count(".") > 1 }
         | 
| 114 120 | 
             
                end
         | 
| 115 121 |  | 
| 116 122 | 
             
                def layout_exists?(name)
         | 
| @@ -163,7 +169,8 @@ module Brakeman | |
| 163 169 | 
             
                def select_files(paths)
         | 
| 164 170 | 
             
                  paths = select_only_files(paths)
         | 
| 165 171 | 
             
                  paths = reject_skipped_files(paths)
         | 
| 166 | 
            -
                  convert_to_file_paths(paths)
         | 
| 172 | 
            +
                  paths = convert_to_file_paths(paths)
         | 
| 173 | 
            +
                  reject_global_excludes(paths)
         | 
| 167 174 | 
             
                end
         | 
| 168 175 |  | 
| 169 176 | 
             
                def select_only_files(paths)
         | 
| @@ -182,6 +189,32 @@ module Brakeman | |
| 182 189 | 
             
                  end
         | 
| 183 190 | 
             
                end
         | 
| 184 191 |  | 
| 192 | 
            +
                EXCLUDED_PATHS = %w[
         | 
| 193 | 
            +
                  /generators/
         | 
| 194 | 
            +
                  lib/tasks/
         | 
| 195 | 
            +
                  lib/templates/
         | 
| 196 | 
            +
                  db/
         | 
| 197 | 
            +
                  spec/
         | 
| 198 | 
            +
                  test/
         | 
| 199 | 
            +
                  tmp/
         | 
| 200 | 
            +
                  public/
         | 
| 201 | 
            +
                  log/
         | 
| 202 | 
            +
                ]
         | 
| 203 | 
            +
             | 
| 204 | 
            +
                def reject_global_excludes(paths)
         | 
| 205 | 
            +
                  paths.reject do |path|
         | 
| 206 | 
            +
                    relative_path = path.relative
         | 
| 207 | 
            +
             | 
| 208 | 
            +
                    if @skip_vendor and relative_path.include? 'vendor/'
         | 
| 209 | 
            +
                      true
         | 
| 210 | 
            +
                    else
         | 
| 211 | 
            +
                      EXCLUDED_PATHS.any? do |excluded|
         | 
| 212 | 
            +
                        relative_path.include? excluded
         | 
| 213 | 
            +
                      end
         | 
| 214 | 
            +
                    end
         | 
| 215 | 
            +
                  end
         | 
| 216 | 
            +
                end
         | 
| 217 | 
            +
             | 
| 185 218 | 
             
                def match_path files, path
         | 
| 186 219 | 
             
                  absolute_path = Pathname.new(path)
         | 
| 187 220 | 
             
                  # relative root never has a leading separator. But, we use a leading
         | 
| @@ -0,0 +1,68 @@ | |
| 1 | 
            +
            require 'brakeman/checks/base_check'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class Brakeman::CheckUnsafeReflectionMethods < Brakeman::BaseCheck
         | 
| 4 | 
            +
              Brakeman::Checks.add self
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              @description = "Checks for unsafe reflection to access methods"
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              def run_check
         | 
| 9 | 
            +
                check_method
         | 
| 10 | 
            +
                check_tap
         | 
| 11 | 
            +
                check_to_proc
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              def check_method
         | 
| 15 | 
            +
                tracker.find_call(method: :method, nested: true).each do |result|
         | 
| 16 | 
            +
                  argument = result[:call].first_arg
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  if user_input = include_user_input?(argument)
         | 
| 19 | 
            +
                    warn_unsafe_reflection(result, user_input)
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              def check_tap
         | 
| 25 | 
            +
                tracker.find_call(method: :tap, nested: true).each do |result|
         | 
| 26 | 
            +
                  argument = result[:call].first_arg
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  # Argument is passed like a.tap(&argument)
         | 
| 29 | 
            +
                  if node_type? argument, :block_pass
         | 
| 30 | 
            +
                    argument = argument.value
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  if user_input = include_user_input?(argument)
         | 
| 34 | 
            +
                    warn_unsafe_reflection(result, user_input)
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              def check_to_proc
         | 
| 40 | 
            +
                tracker.find_call(method: :to_proc, nested: true).each do |result|
         | 
| 41 | 
            +
                  target = result[:call].target
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  if user_input = include_user_input?(target)
         | 
| 44 | 
            +
                    warn_unsafe_reflection(result, user_input)
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
              def warn_unsafe_reflection result, input
         | 
| 50 | 
            +
                return unless original? result
         | 
| 51 | 
            +
                method = result[:call].method
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                confidence = if input.type == :params
         | 
| 54 | 
            +
                  :high
         | 
| 55 | 
            +
                else
         | 
| 56 | 
            +
                  :medium
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                message = msg("Unsafe reflection method ", msg_code(method), " called with ", msg_input(input))
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                warn :result => result,
         | 
| 62 | 
            +
                  :warning_type => "Remote Code Execution",
         | 
| 63 | 
            +
                  :warning_code => :unsafe_method_reflection,
         | 
| 64 | 
            +
                  :message => message,
         | 
| 65 | 
            +
                  :user_input => input,
         | 
| 66 | 
            +
                  :confidence => confidence
         | 
| 67 | 
            +
              end
         | 
| 68 | 
            +
            end
         | 
| @@ -0,0 +1,75 @@ | |
| 1 | 
            +
            require 'brakeman/checks/base_check'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class Brakeman::CheckVerbConfusion < Brakeman::BaseCheck
         | 
| 4 | 
            +
              Brakeman::Checks.add self
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              @description = "Check for uses of `request.get?` that might have unintentional behavior"
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              #Process calls
         | 
| 9 | 
            +
              def run_check
         | 
| 10 | 
            +
                calls = tracker.find_call(target: :request, methods: [:get?])
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                calls.each do |call|
         | 
| 13 | 
            +
                  process_result call
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              def process_result result
         | 
| 18 | 
            +
                @current_result = result
         | 
| 19 | 
            +
                @matched_call = result[:call]
         | 
| 20 | 
            +
                klass = tracker.find_class(result[:location][:class])
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                # TODO: abstract into tracker.find_location ?
         | 
| 23 | 
            +
                if klass.nil?
         | 
| 24 | 
            +
                  Brakeman.debug "No class found: #{result[:location][:class]}"
         | 
| 25 | 
            +
                  return
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                method = klass.get_method(result[:location][:method])
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                if method.nil?
         | 
| 31 | 
            +
                  Brakeman.debug "No method found: #{result[:location][:method]}"
         | 
| 32 | 
            +
                  return
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                process method[:src]
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
              def process_if exp
         | 
| 39 | 
            +
                if exp.condition == @matched_call
         | 
| 40 | 
            +
                  # Found `if request.get?`
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  # Do not warn if there is an `elsif` clause
         | 
| 43 | 
            +
                  if node_type? exp.else_clause, :if
         | 
| 44 | 
            +
                    return exp
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  warn_about_result @current_result, exp
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                exp
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
              def warn_about_result result, code
         | 
| 54 | 
            +
                return unless original? result
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                confidence = :weak
         | 
| 57 | 
            +
                message = msg('Potential HTTP verb confusion. ',
         | 
| 58 | 
            +
                              msg_code('HEAD'),
         | 
| 59 | 
            +
                              ' is routed like ',
         | 
| 60 | 
            +
                              msg_code('GET'),
         | 
| 61 | 
            +
                              ' but ',
         | 
| 62 | 
            +
                              msg_code('request.get?'),
         | 
| 63 | 
            +
                              ' will return ',
         | 
| 64 | 
            +
                              msg_code('false')
         | 
| 65 | 
            +
                             )
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                warn :result => result,
         | 
| 68 | 
            +
                  :warning_type => "HTTP Verb Confusion",
         | 
| 69 | 
            +
                  :warning_code => :http_verb_confusion,
         | 
| 70 | 
            +
                  :message => message,
         | 
| 71 | 
            +
                  :code => code,
         | 
| 72 | 
            +
                  :user_input => result[:call],
         | 
| 73 | 
            +
                  :confidence => confidence
         | 
| 74 | 
            +
              end
         | 
| 75 | 
            +
            end
         | 
    
        data/lib/brakeman/file_parser.rb
    CHANGED
    
    | @@ -3,32 +3,31 @@ module Brakeman | |
| 3 3 |  | 
| 4 4 | 
             
              # This class handles reading and parsing files.
         | 
| 5 5 | 
             
              class FileParser
         | 
| 6 | 
            -
                attr_reader :file_list
         | 
| 6 | 
            +
                attr_reader :file_list, :errors
         | 
| 7 7 |  | 
| 8 | 
            -
                def initialize  | 
| 9 | 
            -
                  @ | 
| 10 | 
            -
                  @timeout =  | 
| 11 | 
            -
                  @ | 
| 12 | 
            -
                  @ | 
| 8 | 
            +
                def initialize app_tree, timeout
         | 
| 9 | 
            +
                  @app_tree = app_tree
         | 
| 10 | 
            +
                  @timeout = timeout
         | 
| 11 | 
            +
                  @file_list = []
         | 
| 12 | 
            +
                  @errors = []
         | 
| 13 13 | 
             
                end
         | 
| 14 14 |  | 
| 15 | 
            -
                def parse_files list | 
| 16 | 
            -
                  read_files list | 
| 15 | 
            +
                def parse_files list
         | 
| 16 | 
            +
                  read_files list do |path, contents|
         | 
| 17 17 | 
             
                    if ast = parse_ruby(contents, path.relative)
         | 
| 18 18 | 
             
                      ASTFile.new(path, ast)
         | 
| 19 19 | 
             
                    end
         | 
| 20 20 | 
             
                  end
         | 
| 21 21 | 
             
                end
         | 
| 22 22 |  | 
| 23 | 
            -
                def read_files list | 
| 24 | 
            -
                  @file_list[type] ||= []
         | 
| 25 | 
            -
             | 
| 23 | 
            +
                def read_files list
         | 
| 26 24 | 
             
                  list.each do |path|
         | 
| 27 25 | 
             
                    file = @app_tree.file_path(path)
         | 
| 28 26 |  | 
| 29 27 | 
             
                    result = yield file, file.read
         | 
| 28 | 
            +
             | 
| 30 29 | 
             
                    if result
         | 
| 31 | 
            -
                      @file_list | 
| 30 | 
            +
                      @file_list << result
         | 
| 32 31 | 
             
                    end
         | 
| 33 32 | 
             
                  end
         | 
| 34 33 | 
             
                end
         | 
| @@ -38,15 +37,17 @@ module Brakeman | |
| 38 37 | 
             
                    Brakeman.debug "Parsing #{path}"
         | 
| 39 38 | 
             
                    RubyParser.new.parse input, path, @timeout
         | 
| 40 39 | 
             
                  rescue Racc::ParseError => e
         | 
| 41 | 
            -
                     | 
| 42 | 
            -
                    nil
         | 
| 40 | 
            +
                    error e.exception(e.message + "\nCould not parse #{path}")
         | 
| 43 41 | 
             
                  rescue Timeout::Error => e
         | 
| 44 | 
            -
                     | 
| 45 | 
            -
                    nil
         | 
| 42 | 
            +
                    error Exception.new("Parsing #{path} took too long (> #{@timeout} seconds). Try increasing the limit with --parser-timeout")
         | 
| 46 43 | 
             
                  rescue => e
         | 
| 47 | 
            -
                     | 
| 48 | 
            -
                    nil
         | 
| 44 | 
            +
                    error e.exception(e.message + "\nWhile processing #{path}")
         | 
| 49 45 | 
             
                  end
         | 
| 50 46 | 
             
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def error exception
         | 
| 49 | 
            +
                  @errors << exception
         | 
| 50 | 
            +
                  nil
         | 
| 51 | 
            +
                end
         | 
| 51 52 | 
             
              end
         | 
| 52 53 | 
             
            end
         | 
    
        data/lib/brakeman/options.rb
    CHANGED
    
    | @@ -166,6 +166,10 @@ module Brakeman::Options | |
| 166 166 | 
             
                      options[:only_files].merge files
         | 
| 167 167 | 
             
                    end
         | 
| 168 168 |  | 
| 169 | 
            +
                    opts.on "--[no-]skip-vendor", "Skip processing vendor directory (Default)" do |skip|
         | 
| 170 | 
            +
                      options[:skip_vendor] = skip
         | 
| 171 | 
            +
                    end
         | 
| 172 | 
            +
             | 
| 169 173 | 
             
                    opts.on "--skip-libs", "Skip processing lib directory" do
         | 
| 170 174 | 
             
                      options[:skip_libs] = true
         | 
| 171 175 | 
             
                    end
         | 
| @@ -229,7 +233,7 @@ module Brakeman::Options | |
| 229 233 |  | 
| 230 234 | 
             
                    opts.on "-f",
         | 
| 231 235 | 
             
                      "--format TYPE",
         | 
| 232 | 
            -
                      [:pdf, :text, :html, :csv, :tabs, :json, :markdown, :codeclimate, :cc, :plain, :table, :junit, :sarif],
         | 
| 236 | 
            +
                      [:pdf, :text, :html, :csv, :tabs, :json, :markdown, :codeclimate, :cc, :plain, :table, :junit, :sarif, :sonar],
         | 
| 233 237 | 
             
                      "Specify output formats. Default is text" do |type|
         | 
| 234 238 |  | 
| 235 239 | 
             
                      type = "s" if type == :text
         | 
| @@ -9,7 +9,6 @@ module Brakeman | |
| 9 9 | 
             
                def initialize tracker, file_parser
         | 
| 10 10 | 
             
                  @tracker = tracker
         | 
| 11 11 | 
             
                  @file_parser = file_parser
         | 
| 12 | 
            -
                  @file_parser.file_list[:templates] ||= []
         | 
| 13 12 | 
             
                end
         | 
| 14 13 |  | 
| 15 14 | 
             
                def parse_template path, text
         | 
| @@ -33,7 +32,7 @@ module Brakeman | |
| 33 32 | 
             
                          end
         | 
| 34 33 |  | 
| 35 34 | 
             
                    if src and ast = @file_parser.parse_ruby(src, path)
         | 
| 36 | 
            -
                      @file_parser.file_list | 
| 35 | 
            +
                      @file_parser.file_list << TemplateFile.new(path, ast, name, type)
         | 
| 37 36 | 
             
                    end
         | 
| 38 37 | 
             
                  rescue Racc::ParseError => e
         | 
| 39 38 | 
             
                    tracker.error e, "Could not parse #{path}"
         | 
| @@ -97,7 +96,7 @@ module Brakeman | |
| 97 96 | 
             
                end
         | 
| 98 97 |  | 
| 99 98 | 
             
                def self.parse_inline_erb tracker, text
         | 
| 100 | 
            -
                  fp = Brakeman::FileParser.new(tracker)
         | 
| 99 | 
            +
                  fp = Brakeman::FileParser.new(tracker.app_tree, tracker.options[:parser_timeout])
         | 
| 101 100 | 
             
                  tp = self.new(tracker, fp)
         | 
| 102 101 | 
             
                  src = tp.parse_erb '_inline_', text
         | 
| 103 102 | 
             
                  type = tp.erubis? ? :erubis : :erb
         | 
| @@ -76,6 +76,13 @@ class Brakeman::HamlTemplateProcessor < Brakeman::TemplateProcessor | |
| 76 76 | 
             
                end
         | 
| 77 77 | 
             
              end
         | 
| 78 78 |  | 
| 79 | 
            +
              ESCAPE_METHODS = [
         | 
| 80 | 
            +
                :html_escape,
         | 
| 81 | 
            +
                :html_escape_without_haml_xss,
         | 
| 82 | 
            +
                :escape_once,
         | 
| 83 | 
            +
                :escape_once_without_haml_xss
         | 
| 84 | 
            +
              ]
         | 
| 85 | 
            +
             | 
| 79 86 | 
             
              def get_pushed_value exp, default = :output
         | 
| 80 87 | 
             
                return exp unless sexp? exp
         | 
| 81 88 |  | 
| @@ -105,7 +112,7 @@ class Brakeman::HamlTemplateProcessor < Brakeman::TemplateProcessor | |
| 105 112 | 
             
                when :call
         | 
| 106 113 | 
             
                  if exp.method == :to_s or exp.method == :strip
         | 
| 107 114 | 
             
                    get_pushed_value(exp.target, default)
         | 
| 108 | 
            -
                  elsif haml_helpers? exp.target and exp.method | 
| 115 | 
            +
                  elsif haml_helpers? exp.target and ESCAPE_METHODS.include? exp.method
         | 
| 109 116 | 
             
                    get_pushed_value(exp.first_arg, :escaped_output)
         | 
| 110 117 | 
             
                  elsif @javascript and (exp.method == :j or exp.method == :escape_javascript) # TODO: Remove - this is not safe
         | 
| 111 118 | 
             
                    get_pushed_value(exp.first_arg, :escaped_output)
         | 
| @@ -0,0 +1,64 @@ | |
| 1 | 
            +
            module Brakeman
         | 
| 2 | 
            +
              class FileTypeDetector < BaseProcessor
         | 
| 3 | 
            +
                def initialize
         | 
| 4 | 
            +
                  super(nil)
         | 
| 5 | 
            +
                  reset
         | 
| 6 | 
            +
                end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def detect_type(file)
         | 
| 9 | 
            +
                  reset
         | 
| 10 | 
            +
                  process(file.ast)
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  if @file_type.nil?
         | 
| 13 | 
            +
                    @file_type = guess_from_path(file.path.relative)
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  @file_type || :libs
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                MODEL_CLASSES = [
         | 
| 20 | 
            +
                  :'ActiveRecord::Base',
         | 
| 21 | 
            +
                  :ApplicationRecord
         | 
| 22 | 
            +
                ]
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def process_class exp
         | 
| 25 | 
            +
                  name = class_name(exp.class_name)
         | 
| 26 | 
            +
                  parent = class_name(exp.parent_name)
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  if name.match(/Controller$/)
         | 
| 29 | 
            +
                    @file_type = :controllers
         | 
| 30 | 
            +
                    return exp
         | 
| 31 | 
            +
                  elsif MODEL_CLASSES.include? parent
         | 
| 32 | 
            +
                    @file_type = :models
         | 
| 33 | 
            +
                    return exp
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  super
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def guess_from_path path
         | 
| 40 | 
            +
                  case
         | 
| 41 | 
            +
                  when path.include?('app/models')
         | 
| 42 | 
            +
                    :models
         | 
| 43 | 
            +
                  when path.include?('app/controllers')
         | 
| 44 | 
            +
                    :controllers
         | 
| 45 | 
            +
                  when path.include?('config/initializers')
         | 
| 46 | 
            +
                    :initializers
         | 
| 47 | 
            +
                  when path.include?('lib/')
         | 
| 48 | 
            +
                    :libs
         | 
| 49 | 
            +
                  when path.match?(%r{config/environments/(?!production\.rb)$})
         | 
| 50 | 
            +
                    :skip
         | 
| 51 | 
            +
                  when path.match?(%r{environments/production\.rb$})
         | 
| 52 | 
            +
                    :skip
         | 
| 53 | 
            +
                  when path.match?(%r{application\.rb$})
         | 
| 54 | 
            +
                    :skip
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                private
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                def reset
         | 
| 61 | 
            +
                  @file_type = nil
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
            end
         | 
    
        data/lib/brakeman/report.rb
    CHANGED
    
    | @@ -45,6 +45,9 @@ class Brakeman::Report | |
| 45 45 | 
             
                  Brakeman::Report::JUnit
         | 
| 46 46 | 
             
                when :to_sarif
         | 
| 47 47 | 
             
                  return self.to_sarif
         | 
| 48 | 
            +
                when :to_sonar
         | 
| 49 | 
            +
                  require_report 'sonar'
         | 
| 50 | 
            +
                  Brakeman::Report::Sonar
         | 
| 48 51 | 
             
                else
         | 
| 49 52 | 
             
                  raise "Invalid format: #{format}. Should be one of #{VALID_FORMATS.inspect}"
         | 
| 50 53 | 
             
                end
         | 
| @@ -69,6 +72,11 @@ class Brakeman::Report | |
| 69 72 | 
             
                generate Brakeman::Report::JSON
         | 
| 70 73 | 
             
              end
         | 
| 71 74 |  | 
| 75 | 
            +
              def to_sonar
         | 
| 76 | 
            +
                require_report 'sonar'
         | 
| 77 | 
            +
                generate Brakeman::Report::Sonar
         | 
| 78 | 
            +
              end
         | 
| 79 | 
            +
             | 
| 72 80 | 
             
              def to_table
         | 
| 73 81 | 
             
                require_report 'table'
         | 
| 74 82 | 
             
                generate Brakeman::Report::Table
         | 
| @@ -0,0 +1,38 @@ | |
| 1 | 
            +
            class Brakeman::Report::Sonar < Brakeman::Report::Base
         | 
| 2 | 
            +
              def generate_report
         | 
| 3 | 
            +
                report_object = {
         | 
| 4 | 
            +
                  issues: all_warnings.map { |warning| issue_json(warning) }
         | 
| 5 | 
            +
                }
         | 
| 6 | 
            +
                return JSON.pretty_generate report_object
         | 
| 7 | 
            +
              end
         | 
| 8 | 
            +
              
         | 
| 9 | 
            +
              private
         | 
| 10 | 
            +
              
         | 
| 11 | 
            +
              def issue_json(warning)
         | 
| 12 | 
            +
                {
         | 
| 13 | 
            +
                  engineId: "Brakeman",
         | 
| 14 | 
            +
                  ruleId: warning.warning_code,
         | 
| 15 | 
            +
                  type: "VULNERABILITY",
         | 
| 16 | 
            +
                  severity: severity_level_for(warning.confidence),
         | 
| 17 | 
            +
                  primaryLocation: {
         | 
| 18 | 
            +
                    message: warning.message,
         | 
| 19 | 
            +
                    filePath: warning.file.relative,
         | 
| 20 | 
            +
                    textRange: {
         | 
| 21 | 
            +
                      "startLine": warning.line || 1,
         | 
| 22 | 
            +
                      "endLine": warning.line || 1,
         | 
| 23 | 
            +
                    }
         | 
| 24 | 
            +
                  },
         | 
| 25 | 
            +
                  effortMinutes: (4 - warning.confidence) * 15
         | 
| 26 | 
            +
                }
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              def severity_level_for(confidence)
         | 
| 30 | 
            +
                if confidence == 0
         | 
| 31 | 
            +
                  "CRITICAL"
         | 
| 32 | 
            +
                elsif confidence == 1
         | 
| 33 | 
            +
                  "MAJOR"
         | 
| 34 | 
            +
                else
         | 
| 35 | 
            +
                  "MINOR"
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
            end
         | 
    
        data/lib/brakeman/rescanner.rb
    CHANGED
    
    | @@ -132,10 +132,11 @@ class Brakeman::Rescanner < Brakeman::Scanner | |
| 132 132 | 
             
                template_name = template_path_to_name(path)
         | 
| 133 133 |  | 
| 134 134 | 
             
                tracker.reset_template template_name
         | 
| 135 | 
            -
                fp = Brakeman::FileParser.new(tracker)
         | 
| 135 | 
            +
                fp = Brakeman::FileParser.new(tracker.app_tree, tracker.options[:parser_timeout])
         | 
| 136 136 | 
             
                template_parser = Brakeman::TemplateParser.new(tracker, fp)
         | 
| 137 137 | 
             
                template_parser.parse_template path, path.read
         | 
| 138 | 
            -
                 | 
| 138 | 
            +
                tracker.add_errors(fp.errors)
         | 
| 139 | 
            +
                process_template fp.file_list.first
         | 
| 139 140 |  | 
| 140 141 | 
             
                @processor.process_template_alias tracker.templates[template_name]
         | 
| 141 142 |  | 
| @@ -390,9 +391,10 @@ class Brakeman::Rescanner < Brakeman::Scanner | |
| 390 391 |  | 
| 391 392 | 
             
              def parse_ruby_files list
         | 
| 392 393 | 
             
                paths = list.select(&:exists?)
         | 
| 393 | 
            -
                file_parser = Brakeman::FileParser.new(tracker)
         | 
| 394 | 
            -
                file_parser.parse_files paths | 
| 395 | 
            -
                file_parser. | 
| 394 | 
            +
                file_parser = Brakeman::FileParser.new(tracker.app_tree, tracker.options[:parser_timeout])
         | 
| 395 | 
            +
                file_parser.parse_files paths
         | 
| 396 | 
            +
                tracker.add_errors(file_parser.errors)
         | 
| 397 | 
            +
                file_parser.file_list
         | 
| 396 398 | 
             
              end
         | 
| 397 399 | 
             
            end
         | 
| 398 400 |  | 
    
        data/lib/brakeman/scanner.rb
    CHANGED
    
    | @@ -7,6 +7,7 @@ begin | |
| 7 7 | 
             
              require 'brakeman/app_tree'
         | 
| 8 8 | 
             
              require 'brakeman/file_parser'
         | 
| 9 9 | 
             
              require 'brakeman/parsers/template_parser'
         | 
| 10 | 
            +
              require 'brakeman/processors/lib/file_type_detector'
         | 
| 10 11 | 
             
            rescue LoadError => e
         | 
| 11 12 | 
             
              $stderr.puts e.message
         | 
| 12 13 | 
             
              $stderr.puts "Please install the appropriate dependency."
         | 
| @@ -23,7 +24,10 @@ class Brakeman::Scanner | |
| 23 24 | 
             
                @app_tree = Brakeman::AppTree.from_options(options)
         | 
| 24 25 |  | 
| 25 26 | 
             
                if (!@app_tree.root || !@app_tree.exists?("app")) && !options[:force_scan]
         | 
| 26 | 
            -
                   | 
| 27 | 
            +
                  message = "Please supply the path to a Rails application (looking in #{@app_tree.root}).\n" <<
         | 
| 28 | 
            +
                            "  Use `--force` to run a scan anyway - for example if there are many applications in one directory."
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  raise Brakeman::NoApplication, message
         | 
| 27 31 | 
             
                end
         | 
| 28 32 |  | 
| 29 33 | 
             
                @processor = processor || Brakeman::Processor.new(@app_tree, options)
         | 
| @@ -43,6 +47,8 @@ class Brakeman::Scanner | |
| 43 47 | 
             
                process_config
         | 
| 44 48 | 
             
                Brakeman.notify "Parsing files..."
         | 
| 45 49 | 
             
                parse_files
         | 
| 50 | 
            +
                Brakeman.notify "Detecting file types..."
         | 
| 51 | 
            +
                detect_file_types
         | 
| 46 52 | 
             
                Brakeman.notify "Processing initializers..."
         | 
| 47 53 | 
             
                process_initializers
         | 
| 48 54 | 
             
                Brakeman.notify "Processing libs..."
         | 
| @@ -65,29 +71,47 @@ class Brakeman::Scanner | |
| 65 71 | 
             
              end
         | 
| 66 72 |  | 
| 67 73 | 
             
              def parse_files
         | 
| 68 | 
            -
                fp = Brakeman::FileParser.new tracker
         | 
| 69 | 
            -
             | 
| 70 | 
            -
                files = {
         | 
| 71 | 
            -
                  :initializers => @app_tree.initializer_paths,
         | 
| 72 | 
            -
                  :controllers => @app_tree.controller_paths,
         | 
| 73 | 
            -
                  :models => @app_tree.model_paths
         | 
| 74 | 
            -
                }
         | 
| 75 | 
            -
             | 
| 76 | 
            -
                unless options[:skip_libs]
         | 
| 77 | 
            -
                  files[:libs] = @app_tree.lib_paths
         | 
| 78 | 
            -
                end
         | 
| 74 | 
            +
                fp = Brakeman::FileParser.new(tracker.app_tree, tracker.options[:parser_timeout])
         | 
| 79 75 |  | 
| 80 | 
            -
                 | 
| 81 | 
            -
                  fp.parse_files paths, name
         | 
| 82 | 
            -
                end
         | 
| 76 | 
            +
                fp.parse_files tracker.app_tree.ruby_file_paths
         | 
| 83 77 |  | 
| 84 78 | 
             
                template_parser = Brakeman::TemplateParser.new(tracker, fp)
         | 
| 85 79 |  | 
| 86 | 
            -
                fp.read_files(@app_tree.template_paths | 
| 80 | 
            +
                fp.read_files(@app_tree.template_paths) do |path, contents|
         | 
| 87 81 | 
             
                  template_parser.parse_template path, contents
         | 
| 88 82 | 
             
                end
         | 
| 89 83 |  | 
| 90 | 
            -
                 | 
| 84 | 
            +
                # Collect errors raised during parsing
         | 
| 85 | 
            +
                tracker.add_errors(fp.errors)
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                @parsed_files = fp.file_list
         | 
| 88 | 
            +
              end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
              def detect_file_types
         | 
| 91 | 
            +
                @file_list = {
         | 
| 92 | 
            +
                  controllers: [],
         | 
| 93 | 
            +
                  initializers: [],
         | 
| 94 | 
            +
                  libs: [],
         | 
| 95 | 
            +
                  models: [],
         | 
| 96 | 
            +
                  templates: [],
         | 
| 97 | 
            +
                }
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                detector = Brakeman::FileTypeDetector.new
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                @parsed_files.each do |file|
         | 
| 102 | 
            +
                  if file.is_a? Brakeman::TemplateParser::TemplateFile
         | 
| 103 | 
            +
                    @file_list[:templates] << file
         | 
| 104 | 
            +
                  else
         | 
| 105 | 
            +
                    type = detector.detect_type(file)
         | 
| 106 | 
            +
                    unless type == :skip
         | 
| 107 | 
            +
                      if @file_list[type].nil?
         | 
| 108 | 
            +
                        raise type.to_s
         | 
| 109 | 
            +
                      else
         | 
| 110 | 
            +
                        @file_list[type] << file
         | 
| 111 | 
            +
                      end
         | 
| 112 | 
            +
                    end
         | 
| 113 | 
            +
                  end
         | 
| 114 | 
            +
                end
         | 
| 91 115 | 
             
              end
         | 
| 92 116 |  | 
| 93 117 | 
             
              #Process config/environment.rb and config/gems.rb
         | 
| @@ -325,7 +349,7 @@ class Brakeman::Scanner | |
| 325 349 | 
             
              end
         | 
| 326 350 |  | 
| 327 351 | 
             
              def parse_ruby_file file
         | 
| 328 | 
            -
                fp = Brakeman::FileParser.new( | 
| 352 | 
            +
                fp = Brakeman::FileParser.new(tracker.app_tree, tracker.options[:parser_timeout])
         | 
| 329 353 | 
             
                fp.parse_ruby(file.read, file)
         | 
| 330 354 | 
             
              end
         | 
| 331 355 | 
             
            end
         | 
    
        data/lib/brakeman/tracker.rb
    CHANGED
    
    
    
        data/lib/brakeman/util.rb
    CHANGED
    
    | @@ -368,8 +368,13 @@ module Brakeman::Util | |
| 368 368 | 
             
              #
         | 
| 369 369 | 
             
              # views/test/something.html.erb -> test/something
         | 
| 370 370 | 
             
              def template_path_to_name path
         | 
| 371 | 
            -
                names = path.relative.split( | 
| 371 | 
            +
                names = path.relative.split('/')
         | 
| 372 372 | 
             
                names.last.gsub!(/(\.(html|js)\..*|\.(rhtml|haml|erb|slim))$/, '')
         | 
| 373 | 
            -
             | 
| 373 | 
            +
             | 
| 374 | 
            +
                if names.include? 'views'
         | 
| 375 | 
            +
                  names[(names.index('views') + 1)..-1]
         | 
| 376 | 
            +
                else
         | 
| 377 | 
            +
                  names
         | 
| 378 | 
            +
                end.join('/').to_sym
         | 
| 374 379 | 
             
              end
         | 
| 375 380 | 
             
            end
         | 
    
        data/lib/brakeman/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: brakeman-lib
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version:  | 
| 4 | 
            +
              version: 5.0.0.pre1
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Justin Collins
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2020- | 
| 11 | 
            +
            date: 2020-11-18 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: minitest
         | 
| @@ -184,14 +184,14 @@ dependencies: | |
| 184 184 | 
             
                requirements:
         | 
| 185 185 | 
             
                - - "~>"
         | 
| 186 186 | 
             
                  - !ruby/object:Gem::Version
         | 
| 187 | 
            -
                    version: 5.1 | 
| 187 | 
            +
                    version: '5.1'
         | 
| 188 188 | 
             
              type: :runtime
         | 
| 189 189 | 
             
              prerelease: false
         | 
| 190 190 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 191 191 | 
             
                requirements:
         | 
| 192 192 | 
             
                - - "~>"
         | 
| 193 193 | 
             
                  - !ruby/object:Gem::Version
         | 
| 194 | 
            -
                    version: 5.1 | 
| 194 | 
            +
                    version: '5.1'
         | 
| 195 195 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 196 196 | 
             
              name: slim
         | 
| 197 197 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -301,8 +301,10 @@ files: | |
| 301 301 | 
             
            - lib/brakeman/checks/check_template_injection.rb
         | 
| 302 302 | 
             
            - lib/brakeman/checks/check_translate_bug.rb
         | 
| 303 303 | 
             
            - lib/brakeman/checks/check_unsafe_reflection.rb
         | 
| 304 | 
            +
            - lib/brakeman/checks/check_unsafe_reflection_methods.rb
         | 
| 304 305 | 
             
            - lib/brakeman/checks/check_unscoped_find.rb
         | 
| 305 306 | 
             
            - lib/brakeman/checks/check_validation_regex.rb
         | 
| 307 | 
            +
            - lib/brakeman/checks/check_verb_confusion.rb
         | 
| 306 308 | 
             
            - lib/brakeman/checks/check_weak_hash.rb
         | 
| 307 309 | 
             
            - lib/brakeman/checks/check_without_protection.rb
         | 
| 308 310 | 
             
            - lib/brakeman/checks/check_xml_dos.rb
         | 
| @@ -333,6 +335,7 @@ files: | |
| 333 335 | 
             
            - lib/brakeman/processors/haml_template_processor.rb
         | 
| 334 336 | 
             
            - lib/brakeman/processors/lib/basic_processor.rb
         | 
| 335 337 | 
             
            - lib/brakeman/processors/lib/call_conversion_helper.rb
         | 
| 338 | 
            +
            - lib/brakeman/processors/lib/file_type_detector.rb
         | 
| 336 339 | 
             
            - lib/brakeman/processors/lib/find_all_calls.rb
         | 
| 337 340 | 
             
            - lib/brakeman/processors/lib/find_call.rb
         | 
| 338 341 | 
             
            - lib/brakeman/processors/lib/find_return_value.rb
         | 
| @@ -369,6 +372,7 @@ files: | |
| 369 372 | 
             
            - lib/brakeman/report/report_junit.rb
         | 
| 370 373 | 
             
            - lib/brakeman/report/report_markdown.rb
         | 
| 371 374 | 
             
            - lib/brakeman/report/report_sarif.rb
         | 
| 375 | 
            +
            - lib/brakeman/report/report_sonar.rb
         | 
| 372 376 | 
             
            - lib/brakeman/report/report_table.rb
         | 
| 373 377 | 
             
            - lib/brakeman/report/report_tabs.rb
         | 
| 374 378 | 
             
            - lib/brakeman/report/report_text.rb
         | 
| @@ -418,12 +422,12 @@ required_ruby_version: !ruby/object:Gem::Requirement | |
| 418 422 | 
             
              requirements:
         | 
| 419 423 | 
             
              - - ">="
         | 
| 420 424 | 
             
                - !ruby/object:Gem::Version
         | 
| 421 | 
            -
                  version:  | 
| 425 | 
            +
                  version: 2.4.0
         | 
| 422 426 | 
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 423 427 | 
             
              requirements:
         | 
| 424 | 
            -
              - - " | 
| 428 | 
            +
              - - ">"
         | 
| 425 429 | 
             
                - !ruby/object:Gem::Version
         | 
| 426 | 
            -
                  version:  | 
| 430 | 
            +
                  version: 1.3.1
         | 
| 427 431 | 
             
            requirements: []
         | 
| 428 432 | 
             
            rubygems_version: 3.1.2
         | 
| 429 433 | 
             
            signing_key: 
         |