Module: Brakeman

Defined in:
lib/brakeman.rb,
lib/brakeman/logger.rb,
lib/brakeman/version.rb,
lib/brakeman/app_tree.rb,
lib/brakeman/messages.rb,
lib/brakeman/file_path.rb,
lib/brakeman/processor.rb,
lib/brakeman/commandline.rb,
lib/brakeman/file_parser.rb,
lib/brakeman/report/pager.rb,
lib/brakeman/tracker/model.rb,
lib/brakeman/tracker/config.rb,
lib/brakeman/tracker/library.rb,
lib/brakeman/tracker/template.rb,
lib/brakeman/tracker/constants.rb,
lib/brakeman/tracker/collection.rb,
lib/brakeman/tracker/controller.rb,
lib/brakeman/tracker/file_cache.rb,
lib/brakeman/parsers/rails_erubi.rb,
lib/brakeman/tracker/method_info.rb,
lib/brakeman/report/ignore/config.rb,
lib/brakeman/parsers/haml_embedded.rb,
lib/brakeman/parsers/template_parser.rb,
lib/brakeman/report/ignore/interactive.rb,
lib/brakeman/processors/lib/render_path.rb,
lib/brakeman/processors/lib/safe_call_helper.rb,
lib/brakeman/codeclimate/engine_configuration.rb,
lib/brakeman/processors/lib/file_type_detector.rb,
lib/brakeman/processors/lib/call_conversion_helper.rb

Defined Under Namespace

Modules: CallConversionHelper, Codeclimate, ControllerMethods, FakeHamlFilter, Logger, Messages, ModelMethods, ModuleHelper, Options, ProcessorHelper, RenderHelper, RouteHelper, SafeCallHelper, Util, WarningCodes Classes: ASTFile, AliasProcessor, AppTree, BaseCheck, BaseProcessor, BasicProcessor, CallIndex, CheckBasicAuth, CheckBasicAuthTimingAttack, CheckCSRFTokenForgeryCVE, CheckContentTag, CheckCookieSerialization, CheckCreateWith, CheckCrossSiteScripting, CheckDefaultRoutes, CheckDeserialize, CheckDetailedExceptions, CheckDigestDoS, CheckDivideByZero, CheckDynamicFinders, CheckEOLRails, CheckEOLRuby, CheckEscapeFunction, CheckEvaluation, CheckExecute, CheckFileAccess, CheckFileDisclosure, CheckFilterSkipping, CheckForceSSL, CheckForgerySetting, CheckHeaderDoS, CheckI18nXSS, CheckJRubyXML, CheckJSONEncoding, CheckJSONEntityEscape, CheckJSONParsing, CheckLinkTo, CheckLinkToHref, CheckMailTo, CheckMassAssignment, CheckMimeTypeDoS, CheckModelAttrAccessible, CheckModelAttributes, CheckModelSerialize, CheckNestedAttributes, CheckNestedAttributesBypass, CheckNumberToCurrency, CheckPageCachingCVE, CheckPathname, CheckPermitAttributes, CheckQuoteTableName, CheckRansack, CheckRedirect, CheckRegexDoS, CheckRender, CheckRenderDoS, CheckRenderInline, CheckRenderRCE, CheckResponseSplitting, CheckReverseTabnabbing, CheckRouteDoS, CheckSQL, CheckSQLCVEs, CheckSSLVerify, CheckSafeBufferManipulation, CheckSanitizeConfigCve, CheckSanitizeMethods, CheckSecrets, CheckSelectTag, CheckSelectVulnerability, CheckSend, CheckSendFile, CheckSessionManipulation, CheckSessionSettings, CheckSimpleFormat, CheckSingleQuotes, CheckSkipBeforeFilter, CheckSprocketsPathTraversal, CheckStripTags, CheckSymbolDoS, CheckSymbolDoSCVE, CheckTemplateInjection, CheckTranslateBug, CheckUnsafeReflection, CheckUnsafeReflectionMethods, CheckUnscopedFind, CheckValidationRegex, CheckVerbConfusion, CheckWeakHash, CheckWeakRSAKey, CheckWithoutProtection, CheckXMLDoS, CheckYAMLParsing, Checks, Collection, Commandline, Config, ConfigAliasProcessor, ConfigProcessor, Constant, Constants, Controller, ControllerAliasProcessor, ControllerProcessor, DependencyError, Differ, EOLCheck, ErbTemplateProcessor, Erubi, ErubiTemplateProcessor, FileCache, FileParser, FilePath, FileTypeDetector, FindAllCalls, FindCall, FindReturnValue, GemProcessor, Haml6TemplateProcessor, HamlTemplateProcessor, IgnoreConfig, InteractiveIgnorer, Library, LibraryProcessor, MethodInfo, MissingChecksError, Model, ModelProcessor, NoApplication, NoBrakemanError, OutputProcessor, Pager, Processor, Rails2ConfigProcessor, Rails2RoutesProcessor, Rails3ConfigProcessor, Rails3RoutesProcessor, Rails4ConfigProcessor, RenderPath, Report, RescanReport, Rescanner, RouteAliasProcessor, RoutesProcessor, Scanner, SexpProcessor, SlimTemplateProcessor, Template, TemplateAliasProcessor, TemplateParser, TemplateProcessor, Tracker, Warning

Constant Summary collapse

Warnings_Found_Exit_Code =

This exit code is used when warnings are found and the –exit-on-warn option is set

3
No_App_Found_Exit_Code =

Exit code returned when no Rails application is detected

4
Not_Latest_Version_Exit_Code =

Exit code returned when brakeman was outdated

5
Missing_Checks_Exit_Code =

Exit code returned when user requests non-existent checks

6
Errors_Found_Exit_Code =

Exit code returned when errors were found and the –exit-on-error option is set

7
Empty_Ignore_Note_Exit_Code =

Exit code returned when an ignored warning has no note and –ensure-ignore-notes is set

8
Obsolete_Ignore_Entries_Exit_Code =

Exit code returned when at least one obsolete ignore entry is present and --ensure-no-obsolete-ignore-entries is set.

9
CONFIG_FILES =
begin
  [
    File.expand_path("~/.brakeman/config.yml"),
    File.expand_path("/etc/brakeman/config.yml")
  ]
rescue ArgumentError
  # In case $HOME or $USER aren't defined for use of `~`
  [
    File.expand_path("/etc/brakeman/config.yml")
  ]
end
Version =
"8.0.2"

Class Method Summary collapse

Class Method Details

.add_external_checks(options) ⇒ Object



630
631
632
633
634
# File 'lib/brakeman.rb', line 630

def self.add_external_checks options
  options[:additional_checks_path].each do |path|
    Brakeman::Checks.initialize_checks path
  end if options[:additional_checks_path]
end

.alert(message) ⇒ Object



530
531
532
# File 'lib/brakeman.rb', line 530

def self.alert message
  logger.alert message
end

.announce(message) ⇒ Object



526
527
528
# File 'lib/brakeman.rb', line 526

def self.announce message
  logger.announce message
end

.check_for_missing_checks(included_checks, excluded_checks, enabled_checks) ⇒ Object



636
637
638
639
640
641
642
643
644
# File 'lib/brakeman.rb', line 636

def self.check_for_missing_checks included_checks, excluded_checks, enabled_checks
  checks = included_checks.to_a + excluded_checks.to_a + enabled_checks.to_a

  missing = Brakeman::Checks.missing_checks(checks)

  unless missing.empty?
    raise MissingChecksError, "Could not find specified check#{missing.length > 1 ? 's' : ''}: #{missing.map {|c| "`#{c}`"}.join(', ')}"
  end
end

.cleanup(newline = true) ⇒ Object



122
123
124
# File 'lib/brakeman.rb', line 122

def self.cleanup(newline = true)
  @logger.cleanup(newline) if @logger
end

.compare(options) ⇒ Object

Compare JSON output from a previous scan and return the diff of the two scans

Raises:

  • (ArgumentError)


539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
# File 'lib/brakeman.rb', line 539

def self.compare options
  require 'json'
  require 'brakeman/differ'
  raise ArgumentError.new("Comparison file doesn't exist") unless File.exist? options[:previous_results_json]

  begin
    previous_results = JSON.parse(File.read(options[:previous_results_json]), :symbolize_names => true)[:warnings]
  rescue JSON::ParserError
    self.alert "Error parsing comparison file: #{options[:previous_results_json]}"
    exit!
  end

  tracker = run(options)
  new_report = JSON.parse(tracker.report.to_json, symbolize_names: true)

  new_results = new_report[:warnings]
  obsolete_ignored = tracker.unused_fingerprints

  Brakeman::Differ.new(new_results, previous_results).diff.tap do |diff|
    diff[:obsolete] = obsolete_ignored
  end
end

.config_file(custom_location, app_path) ⇒ Object



220
221
222
223
224
# File 'lib/brakeman.rb', line 220

def self.config_file custom_location, app_path
  app_config = File.expand_path(File.join(app_path, "config", "brakeman.yml"))
  supported_locations = [File.expand_path(custom_location || ""), app_config] + CONFIG_FILES
  supported_locations.detect {|f| File.file?(f) }
end

.debug(message) ⇒ Object



534
535
536
# File 'lib/brakeman.rb', line 534

def self.debug message
  logger.debug message
end

.debug=(val) ⇒ Object



646
647
648
# File 'lib/brakeman.rb', line 646

def self.debug= val
  @debug = val
end

.default_optionsObject

Default set of options



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/brakeman.rb', line 227

def self.default_options
  { :assume_all_routes => true,
    :check_arguments => true,
    :collapse_mass_assignment => false,
    :combine_locations => true,
    :engine_paths => ["engines/*"],
    :exit_on_error => true,
    :exit_on_warn => true,
    :highlight_user_input => true,
    :html_style => "#{File.expand_path(File.dirname(__FILE__))}/brakeman/format/style.css",
    :ignore_model_output => false,
    :ignore_redirect_to_model => true,
    :message_limit => 100,
    :min_confidence => 2,
    :output_color => true,
    :pager => true,
    :parallel_checks => true,
    :parser_timeout => 10,
    :use_prism => true,
    :relative_path => false,
    :report_progress => true,
    :safe_methods => Set.new,
    :show_ignored => false,
    :sql_safe_methods => Set.new,
    :skip_checks => Set.new,
    :skip_vendor => true,
  }
end

.dump_config(options) ⇒ Object

Output configuration to YAML



386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/brakeman.rb', line 386

def self.dump_config options
  require 'yaml'
  if options[:create_config].is_a? String
    file = options[:create_config]
  else
    file = nil
  end

  options.delete :create_config

  if options[:logger]
    @logger = options.delete(:logger)
  else
    set_default_logger(options)
  end

  options.each do |k,v|
    if v.is_a? Set
      options[k] = v.to_a
    end
  end

  if file
    File.open file, "w" do |f|
      YAML.dump options, f
    end

    announce "Output configuration to #{file}"
  else
    $stdout.puts YAML.dump(options)
  end
end

.ensure_latestObject



419
420
421
422
423
424
425
# File 'lib/brakeman.rb', line 419

def self.ensure_latest
  current = Brakeman::Version
  latest = Gem.latest_version_for('brakeman').to_s
  if current != latest
    "Brakeman #{current} is not the latest version #{latest}"
  end
end

.filter_warnings(tracker, options) ⇒ Object



600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
# File 'lib/brakeman.rb', line 600

def self.filter_warnings tracker, options
  require 'brakeman/report/ignore/config'
  config = nil

  app_tree = Brakeman::AppTree.from_options(options)

  if options[:ignore_file]
    file = options[:ignore_file]
  elsif app_tree.exists? "config/brakeman.ignore"
    file = app_tree.expand_path("config/brakeman.ignore")
  elsif not options[:interactive_ignore]
    return
  end

  process_step "Filtering warnings..." do
    if options[:interactive_ignore]
      require 'brakeman/report/ignore/interactive'
      logger.cleanup
      config = InteractiveIgnorer.new(file, tracker.warnings).start
    else
      logger.announce "Using '#{file}' to filter warnings"
      config = IgnoreConfig.new(file, tracker.warnings)
      config.read_from_file
      config.filter_ignored
    end
  end

  tracker.ignored_filter = config
end

.get_output_formats(options) ⇒ Object

Determine output formats based on options or options



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/brakeman.rb', line 258

def self.get_output_formats options
  #Set output format
  if options[:output_format] && options[:output_files] && options[:output_files].size > 1
    raise ArgumentError, "Cannot specify output format if multiple output files specified"
  end
  if options[:output_format]
    get_formats_from_output_format options[:output_format]
  elsif options[:output_files]
    get_formats_from_output_files options[:output_files]
  else
    begin
      self.load_brakeman_dependency 'terminal-table', :allow_fail
      return [:to_s]
    rescue LoadError
      return [:to_json]
    end
  end
end

.ignore_file_entries_with_empty_notes(file) ⇒ Object

Returns an array of alert fingerprints for any ignored warnings without notes found in the specified ignore file (if it exists).



590
591
592
593
594
595
596
597
598
# File 'lib/brakeman.rb', line 590

def self.ignore_file_entries_with_empty_notes file
  return [] unless file

  require 'brakeman/report/ignore/config'

  config = IgnoreConfig.new(file, nil)
  config.read_from_file
  config.already_ignored_entries_with_empty_notes.map { |i| i[:fingerprint] }
end

.list_checks(options) ⇒ Object

Output list of checks (for -k option)



364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
# File 'lib/brakeman.rb', line 364

def self.list_checks options
  require 'brakeman/scanner'

  add_external_checks options

  if options[:list_optional_checks]
    $stderr.puts "Optional Checks:"
    checks = Checks.optional_checks
  else
    $stderr.puts "Available Checks:"
    checks = Checks.checks
  end

  format_length = 30

  $stderr.puts "-" * format_length
  checks.each do |check|
    $stderr.printf("%-#{format_length}s%s\n", check.name, check.description)
  end
end

.load_brakeman_dependency(name, allow_fail = false) ⇒ Object



562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
# File 'lib/brakeman.rb', line 562

def self.load_brakeman_dependency name, allow_fail = false
  return if @loaded_dependencies.include? name

  unless @vendored_paths
    path_load = "#{File.expand_path(File.dirname(__FILE__))}/../bundle/load.rb"

    if File.exist? path_load
      require path_load
    end

    @vendored_paths = true
  end

  begin
    require name
  rescue LoadError => e
    if allow_fail
      raise e
    else
      $stderr.puts e.message
      $stderr.puts "Please install the appropriate dependency: #{name}."
      exit!(-1)
    end
  end
end

.load_options(line_options) ⇒ Object

Load options from YAML file



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/brakeman.rb', line 168

def self.load_options line_options
  custom_location = line_options[:config_file]
  quiet = line_options[:quiet]
  app_path = line_options[:app_path]

  #Load configuration file
  if config = config_file(custom_location, app_path)
    require 'yaml'
    options = YAML.safe_load_file config, permitted_classes: [Symbol], symbolize_names: true

    # Brakeman.logger is probably not set yet
    logger = Brakeman::Logger.get_logger(options || line_options)

    if options
      options.each { |k, v| options[k] = Set.new v if v.is_a? Array }

      # After parsing the yaml config file for options, convert any string keys into symbols.
      options.keys.select {|k| k.is_a? String}.map {|k| k.to_sym }.each {|k| options[k] = options[k.to_s]; options.delete(k.to_s) }

      unless line_options[:allow_check_paths_in_config]
        if options.include? :additional_checks_path
          options.delete :additional_checks_path

          logger.alert 'Ignoring additional check paths in config file. Use --allow-check-paths-in-config to allow' unless (options[:quiet] || quiet)
        end
      end

      # notify if options[:quiet] and quiet is nil||false
      # potentially remove these checks now that logger is used
      logger.alert "Using configuration in #{config}" unless (options[:quiet] || quiet)
      options
    else
      logger.alert "Empty configuration file: #{config}" unless quiet
      {}
    end
  else
    {}
  end
end

.loggerObject



110
111
112
# File 'lib/brakeman.rb', line 110

def self.logger
  @logger
end

.logger=(log) ⇒ Object



114
115
116
# File 'lib/brakeman.rb', line 114

def self.logger= log
  @logger = log
end

.process_step(description) ⇒ Object



654
655
656
# File 'lib/brakeman.rb', line 654

def self.process_step(description, &)
  logger.context(description, &)
end

.quiet=(val) ⇒ Object



650
651
652
# File 'lib/brakeman.rb', line 650

def self.quiet= val
  @quiet = val
end

.rescan(tracker, files, options = {}) ⇒ Object

Rescan a subset of files in a Rails application.

A full scan must have been run already to use this method. The returned Tracker object from Brakeman.run is used as a starting point for the rescan.

Options may be given as a hash with the same values as Brakeman.run. Note that these options will be merged into the Tracker.

This method returns a RescanReport object with information about the scan. However, the Tracker object will also be modified as the scan is run.



515
516
517
518
519
520
521
522
523
524
# File 'lib/brakeman.rb', line 515

def self.rescan tracker, files, options = {}
  require 'brakeman/rescanner'

  options = tracker.options.merge options

  @quiet = !!tracker.options[:quiet]
  @debug = !!tracker.options[:debug]

  Rescanner.new(options, tracker.processor, files).recheck
end

.run(options) ⇒ Object

Run Brakeman scan. Returns Tracker object.

Options:

* :app_path - path to root of Rails app (required)
* :additional_checks_path - array of additional directories containing additional out-of-tree checks to run
* :additional_libs_path - array of additional application relative lib directories (ex. app/mailers) to process
* :assume_all_routes - assume all methods are routes (default: true)
* :check_arguments - check arguments of methods (default: true)
* :collapse_mass_assignment - report unprotected models in single warning (default: false)
* :combine_locations - combine warning locations (default: true)
* :config_file - configuration file
* :escape_html - escape HTML by default (automatic)
* :exit_on_error - only affects Commandline module (default: true)
* :exit_on_warn - only affects Commandline module (default: true)
* :github_repo - github repo to use for file links (user/repo[/path][@ref])
* :highlight_user_input - highlight user input in reported warnings (default: true)
* :html_style - path to CSS file
* :ignore_model_output - consider models safe (default: false)
* :interprocedural - limited interprocedural processing of method calls (default: false)
* :message_limit - limit length of messages
* :min_confidence - minimum confidence (0-2, 0 is highest)
* :output_files - files for output
* :output_formats - formats for output (:to_s, :to_tabs, :to_csv, :to_html)
* :parallel_checks - run checks in parallel (default: true)
* :parser_timeout - set timeout for parsing an individual file (default: 10 seconds)
* :print_report - if no output file specified, print to stdout (default: false)
* :quiet - suppress most messages (default: true)
* :rails3 - force Rails 3 mode (automatic)
* :rails4 - force Rails 4 mode (automatic)
* :rails5 - force Rails 5 mode (automatic)
* :rails6 - force Rails 6 mode (automatic)
* :report_routes - show found routes on controllers (default: false)
* :run_checks - array of checks to run (run all if not specified)
* :safe_methods - array of methods to consider safe
* :show_ignored - Display warnings that are usually ignored
* :sql_safe_methods - array of sql sanitization methods to consider safe
* :skip_vendor - do not process vendor/ directory (default: true)
* :skip_checks - checks not to run (run all if not specified)
* :absolute_paths - show absolute path of each file (default: false)
* :summary_only - only output summary section of report for plain/table (:summary_only, :no_summary, true)

Alternatively, just supply a path as a string.



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/brakeman.rb', line 81

def self.run options
  if not $stderr.tty? and options[:report_progress].nil?
    options[:report_progress] = false
  end

  options = set_options options

  @quiet = !!options[:quiet]
  @debug = !!options[:debug]

  if @quiet
    options[:report_progress] = false
  end

  @logger = options[:logger] || set_default_logger(options)

  if options[:use_prism]
    begin
      require 'prism'
    rescue LoadError => e
      Brakeman.alert "Asked to use Prism, but failed to load: #{e}"
    end
  end

  Brakeman.announce "Brakeman v#{Brakeman::Version}"

  scan options
end

.scan(options) ⇒ Object

Run a scan. Generally called from Brakeman.run instead of directly.



428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
# File 'lib/brakeman.rb', line 428

def self.scan options
  #Load scanner
  scanner, tracker = nil

  process_step 'Loading scanner' do
    begin
      require 'brakeman/scanner'
    rescue LoadError
      raise NoBrakemanError, 'Cannot find lib/ directory.'
    end

    add_external_checks options

    #Start scanning
    scanner = Scanner.new options
    tracker = scanner.tracker

    check_for_missing_checks options[:run_checks], options[:skip_checks], options[:enable_checks]
  end

  logger.announce "Scanning #{tracker.app_path}"
  scanner.process

  tracker.run_checks

  self.filter_warnings tracker, options

  if options[:output_files]
    process_step 'Generating report' do
      write_report_to_files tracker, options[:output_files]
    end
  elsif options[:print_report]
    process_step 'Generating report' do
      write_report_to_formats tracker, options[:output_formats]
    end
  end

  tracker
end

.set_default_logger(options = {}) ⇒ Object



118
119
120
# File 'lib/brakeman.rb', line 118

def self.set_default_logger(options = {})
  @logger = Brakeman::Logger.get_logger(options)
end

.set_options(options) ⇒ Object

Sets up options for run, checks given application path



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/brakeman.rb', line 127

def self.set_options options
  if options.is_a? String
    options = { :app_path => options }
  end

  if options[:quiet] == :command_line
    command_line = true
    options.delete :quiet
  end

  options = default_options.merge(load_options(options)).merge(options)

  if options[:quiet].nil? and not command_line
    options[:quiet] = true
  end

  if options[:rails4]
    options[:rails3] = true
  elsif options[:rails5]
    options[:rails3] = true
    options[:rails4] = true
  elsif options[:rails6]
    options[:rails3] = true
    options[:rails4] = true
    options[:rails5] = true
  end

  options[:output_formats] = get_output_formats options
  options[:github_url] = get_github_url options


  # Use ENV value only if option was not already explicitly set
  # (i.e. prefer commandline option over environment variable).
  if options[:gemfile].nil? and ENV['BUNDLE_GEMFILE'] and not ENV['BUNDLE_GEMFILE'].empty?
    options[:gemfile] = ENV['BUNDLE_GEMFILE']
  end

  options
end