brakeman-lib 4.7.2 → 4.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ef101a185ff582733d1564d862fcb87afbedb1df01482f1c8815f130bd886a0b
4
- data.tar.gz: 938ff3304347e001f5f21880d8d6dfca2bb1d3b26f29dae7d269db67350df70f
3
+ metadata.gz: 34099e8abef9a4c7108905ea8d956d01afbb6037cab597d2ad0beab8790a9060
4
+ data.tar.gz: 982be6bfad0eef60f17627001fef1873bad5a23eef76f687ea434d23773ba9b4
5
5
  SHA512:
6
- metadata.gz: de7d5d8fc614fd226145878158e4e75745e31afce19e87e84e73d19d5182b42128ff36d2a5fa45567286ec989513fa5c1ae40aa53353f56b04c3a7a52a11bef6
7
- data.tar.gz: 78006970c55993fbcf96ac56783d3ed04beb1d5bf54d4b716a04158796398577ded0fb1bd7b7070e91074a6daff48e762b6b49515c10ca6d27c84cde0e7b0531
6
+ metadata.gz: ce68529ca660b85d86b9569b4f8ddfe41c4b7b3bc724d1afc9860f91fa0a945eb473bbd5bb83b4c9d8e61ac6255ab0b11a42878921671a03a0964d2440912178
7
+ data.tar.gz: 1fbd104e129d5fce136d4c4984296a7daa6c88ffd8f22edbb576613cb6519547676f38364a2f728dcc2c9a322119d0024ae845baf7383ecf38b43038e9e129c6
data/CHANGES.md CHANGED
@@ -1,3 +1,15 @@
1
+ # Unreleased
2
+
3
+ * Add JUnit-XML report format (Naoki Kimura)
4
+ * Sort ignore files by fingerprint and line (Ngan Pham)
5
+ * Freeze call index results
6
+ * Fix output test when using newer Minitest
7
+ * Properly render confidence in Markdown report
8
+ * Report old warnings as fixed if zero warnings reported
9
+ * Catch dangerous concatenation in `CheckExecute` (Jacob Evelyn)
10
+ * Show user-friendly message when ignore config file has invalid JSON (D. Hicks)
11
+ * Initialize Rails version with `nil` (Carsten Wirth)
12
+
1
13
  # 4.7.2 - 2019-11-25
2
14
 
3
15
  * Remove version guard for `named_scope` vs. `scope`
@@ -231,6 +231,8 @@ module Brakeman
231
231
  [:to_text]
232
232
  when :table, :to_table
233
233
  [:to_table]
234
+ when :junit, :to_junit
235
+ [:to_junit]
234
236
  else
235
237
  [:to_text]
236
238
  end
@@ -258,6 +260,8 @@ module Brakeman
258
260
  :to_text
259
261
  when /\.table$/i
260
262
  :to_table
263
+ when /\.junit$/i
264
+ :to_junit
261
265
  else
262
266
  :to_text
263
267
  end
@@ -280,15 +280,6 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
280
280
  return location, line
281
281
  end
282
282
 
283
- #Checks if an expression contains string interpolation.
284
- #
285
- #Returns Match with :interp type if found.
286
- def include_interp? exp
287
- @string_interp = false
288
- process exp
289
- @string_interp
290
- end
291
-
292
283
  #Checks if _exp_ includes user input in the form of cookies, parameters,
293
284
  #request environment, or model attributes.
294
285
  #
@@ -504,4 +495,16 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
504
495
 
505
496
  @active_record_models
506
497
  end
498
+
499
+ STRING_METHODS = Set[:<<, :+, :concat, :prepend]
500
+ private_constant :STRING_METHODS
501
+
502
+ def string_building? exp
503
+ return false unless call? exp and STRING_METHODS.include? exp.method
504
+
505
+ node_type? exp.target, :str, :dstr or
506
+ node_type? exp.first_arg, :str, :dstr or
507
+ string_building? exp.target or
508
+ string_building? exp.first_arg
509
+ end
507
510
  end
@@ -55,8 +55,7 @@ class Brakeman::CheckContentTag < Brakeman::CheckCrossSiteScripting
55
55
 
56
56
  @current_file = result[:location][:file]
57
57
 
58
- call = result[:call] = result[:call].dup
59
-
58
+ call = result[:call]
60
59
  args = call.arglist
61
60
 
62
61
  tag_name = args[1]
@@ -56,8 +56,20 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
56
56
 
57
57
  case call.method
58
58
  when :popen
59
- unless array? first_arg
60
- failure = include_user_input?(args) || dangerous_interp?(args)
59
+ # Normally, if we're in a `popen` call, we only are worried about shell
60
+ # injection when the argument is not an array, because array elements
61
+ # are always escaped by Ruby. However, an exception is when the array
62
+ # contains two values are something like "bash -c" because then the third
63
+ # element is effectively the command being run and might be a malicious
64
+ # executable if it comes (partially or fully) from user input.
65
+ if !array?(first_arg)
66
+ failure = include_user_input?(first_arg) ||
67
+ dangerous_interp?(first_arg) ||
68
+ dangerous_string_building?(first_arg)
69
+ elsif dash_c_shell_command?(first_arg[1], first_arg[2])
70
+ failure = include_user_input?(first_arg[3]) ||
71
+ dangerous_interp?(first_arg[3]) ||
72
+ dangerous_string_building?(first_arg[3])
61
73
  end
62
74
  when :system, :exec
63
75
  # Normally, if we're in a `system` or `exec` call, we only are worried
@@ -67,12 +79,18 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
67
79
  # the third argument is effectively the command being run and might be
68
80
  # a malicious executable if it comes (partially or fully) from user input.
69
81
  if dash_c_shell_command?(first_arg, call.second_arg)
70
- failure = include_user_input?(args[3]) || dangerous_interp?(args[3])
82
+ failure = include_user_input?(args[3]) ||
83
+ dangerous_interp?(args[3]) ||
84
+ dangerous_string_building?(args[3])
71
85
  else
72
- failure = include_user_input?(first_arg) || dangerous_interp?(first_arg)
86
+ failure = include_user_input?(first_arg) ||
87
+ dangerous_interp?(first_arg) ||
88
+ dangerous_string_building?(first_arg)
73
89
  end
74
90
  else
75
- failure = include_user_input?(args) || dangerous_interp?(args)
91
+ failure = include_user_input?(args) ||
92
+ dangerous_interp?(args) ||
93
+ dangerous_string_building?(args)
76
94
  end
77
95
 
78
96
  if failure and original? result
@@ -219,6 +237,23 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
219
237
  false
220
238
  end
221
239
 
240
+ #Checks if an expression contains string interpolation.
241
+ #
242
+ #Returns Match with :interp type if found.
243
+ def include_interp? exp
244
+ @string_interp = false
245
+ process exp
246
+ @string_interp
247
+ end
248
+
249
+ def dangerous_string_building? exp
250
+ if string_building?(exp) && res = dangerous?(exp)
251
+ return Match.new(:interp, res)
252
+ end
253
+
254
+ false
255
+ end
256
+
222
257
  def shell_escape? exp
223
258
  return false unless call? exp
224
259
 
@@ -34,7 +34,7 @@ class Brakeman::CheckLinkTo < Brakeman::CheckCrossSiteScripting
34
34
 
35
35
  #Have to make a copy of this, otherwise it will be changed to
36
36
  #an ignored method call by the code above.
37
- call = result[:call] = result[:call].dup
37
+ call = result[:call]
38
38
 
39
39
  first_arg = call.first_arg
40
40
  second_arg = call.second_arg
@@ -30,9 +30,7 @@ class Brakeman::CheckLinkToHref < Brakeman::CheckLinkTo
30
30
  end
31
31
 
32
32
  def process_result result
33
- #Have to make a copy of this, otherwise it will be changed to
34
- #an ignored method call by the code above.
35
- call = result[:call] = result[:call].dup
33
+ call = result[:call]
36
34
  @matched = false
37
35
 
38
36
  url_arg = if result[:block]
@@ -525,8 +525,6 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
525
525
  false
526
526
  end
527
527
 
528
- STRING_METHODS = Set[:<<, :+, :concat, :prepend]
529
-
530
528
  def check_for_string_building exp
531
529
  return unless call? exp
532
530
 
@@ -573,15 +571,6 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
573
571
  end
574
572
  end
575
573
 
576
- def string_building? exp
577
- return false unless call? exp and STRING_METHODS.include? exp.method
578
-
579
- node_type? exp.target, :str, :dstr or
580
- node_type? exp.first_arg, :str, :dstr or
581
- string_building? exp.target or
582
- string_building? exp.first_arg
583
- end
584
-
585
574
  IGNORE_METHODS_IN_SQL = Set[:id, :merge_conditions, :table_name, :quoted_table_name,
586
575
  :quoted_primary_key, :to_i, :to_f, :sanitize_sql, :sanitize_sql_array,
587
576
  :sanitize_sql_for_assignment, :sanitize_sql_for_conditions, :sanitize_sql_hash,
@@ -1,8 +1,6 @@
1
1
  # extracting the diff logic to it's own class for consistency. Currently handles
2
2
  # an array of Brakeman::Warnings or plain hash representations.
3
3
  class Brakeman::Differ
4
- DEFAULT_HASH = {:new => [], :fixed => []}
5
- OLD_WARNING_KEYS = [:warning_type, :location, :code, :message, :file, :link, :confidence, :user_input]
6
4
  attr_reader :old_warnings, :new_warnings
7
5
 
8
6
  def initialize new_warnings, old_warnings
@@ -11,9 +9,6 @@ class Brakeman::Differ
11
9
  end
12
10
 
13
11
  def diff
14
- # get the type of elements
15
- return DEFAULT_HASH if @new_warnings.empty?
16
-
17
12
  warnings = {}
18
13
  warnings[:new] = @new_warnings - @old_warnings
19
14
  warnings[:fixed] = @old_warnings - @new_warnings
@@ -225,7 +225,7 @@ module Brakeman::Options
225
225
 
226
226
  opts.on "-f",
227
227
  "--format TYPE",
228
- [:pdf, :text, :html, :csv, :tabs, :json, :markdown, :codeclimate, :cc, :plain, :table],
228
+ [:pdf, :text, :html, :csv, :tabs, :json, :markdown, :codeclimate, :cc, :plain, :table, :junit],
229
229
  "Specify output formats. Default is text" do |type|
230
230
 
231
231
  type = "s" if type == :text
@@ -60,7 +60,7 @@ class Brakeman::FindAllCalls < Brakeman::BasicProcessor
60
60
  end
61
61
 
62
62
  def process_call exp
63
- @calls << create_call_hash(exp)
63
+ @calls << create_call_hash(exp).freeze
64
64
  exp
65
65
  end
66
66
 
@@ -72,6 +72,7 @@ class Brakeman::FindAllCalls < Brakeman::BasicProcessor
72
72
 
73
73
  call_hash[:block] = exp.block
74
74
  call_hash[:block_args] = exp.block_args
75
+ call_hash.freeze
75
76
 
76
77
  @calls << call_hash
77
78
 
@@ -136,7 +137,7 @@ class Brakeman::FindAllCalls < Brakeman::BasicProcessor
136
137
  :call => exp,
137
138
  :nested => false,
138
139
  :location => make_location,
139
- :parent => @current_call }
140
+ :parent => @current_call }.freeze
140
141
  end
141
142
 
142
143
  #Gets the target of a call as a Symbol
@@ -6,7 +6,7 @@ require 'brakeman/report/report_base'
6
6
  class Brakeman::Report
7
7
  attr_reader :tracker
8
8
 
9
- VALID_FORMATS = [:to_html, :to_pdf, :to_csv, :to_json, :to_tabs, :to_hash, :to_s, :to_markdown, :to_codeclimate, :to_plain, :to_text]
9
+ VALID_FORMATS = [:to_html, :to_pdf, :to_csv, :to_json, :to_tabs, :to_hash, :to_s, :to_markdown, :to_codeclimate, :to_plain, :to_text, :to_junit]
10
10
 
11
11
  def initialize tracker
12
12
  @app_tree = tracker.app_tree
@@ -40,6 +40,9 @@ class Brakeman::Report
40
40
  return self.to_table
41
41
  when :to_pdf
42
42
  raise "PDF output is not yet supported."
43
+ when :to_junit
44
+ require_report 'junit'
45
+ Brakeman::Report::JUnit
43
46
  else
44
47
  raise "Invalid format: #{format}. Should be one of #{VALID_FORMATS.inspect}"
45
48
  end
@@ -97,7 +97,11 @@ module Brakeman
97
97
  # Read configuration to file
98
98
  def read_from_file file = @file
99
99
  if File.exist? file
100
- @already_ignored = JSON.parse(File.read(file), :symbolize_names => true)[:ignored_warnings]
100
+ begin
101
+ @already_ignored = JSON.parse(File.read(file), :symbolize_names => true)[:ignored_warnings]
102
+ rescue => e
103
+ raise e, "\nError[#{e.class}] while reading brakeman ignore file: #{file}\n"
104
+ end
101
105
  else
102
106
  Brakeman.notify "[Notice] Could not find ignore configuration in #{file}"
103
107
  @already_ignored = []
@@ -118,7 +122,7 @@ module Brakeman
118
122
 
119
123
  w[:note] = @notes[w[:fingerprint]] || ""
120
124
  w
121
- end.sort_by { |w| w[:fingerprint] }
125
+ end.sort_by { |w| [w[:fingerprint], w[:line]] }
122
126
 
123
127
  output = {
124
128
  :ignored_warnings => warnings,
@@ -0,0 +1,104 @@
1
+ require 'time'
2
+ require "stringio"
3
+ require 'rexml/document'
4
+
5
+ class Brakeman::Report::JUnit < Brakeman::Report::Base
6
+ def generate_report
7
+ io = StringIO.new
8
+ doc = REXML::Document.new
9
+ doc.add REXML::XMLDecl.new '1.0', 'UTF-8'
10
+
11
+ test_suites = REXML::Element.new 'testsuites'
12
+ test_suites.add_attribute 'xmlns:brakeman', 'https://brakemanscanner.org/'
13
+ properties = test_suites.add_element 'brakeman:properties', { 'xml:id' => 'scan_info' }
14
+ properties.add_element 'brakeman:property', { 'brakeman:name' => 'app_path', 'brakeman:value' => tracker.app_path }
15
+ properties.add_element 'brakeman:property', { 'brakeman:name' => 'rails_version', 'brakeman:value' => rails_version }
16
+ properties.add_element 'brakeman:property', { 'brakeman:name' => 'security_warnings', 'brakeman:value' => all_warnings.length }
17
+ properties.add_element 'brakeman:property', { 'brakeman:name' => 'start_time', 'brakeman:value' => tracker.start_time.iso8601 }
18
+ properties.add_element 'brakeman:property', { 'brakeman:name' => 'end_time', 'brakeman:value' => tracker.end_time.iso8601 }
19
+ properties.add_element 'brakeman:property', { 'brakeman:name' => 'duration', 'brakeman:value' => tracker.duration }
20
+ properties.add_element 'brakeman:property', { 'brakeman:name' => 'checks_performed', 'brakeman:value' => checks.checks_run.join(',') }
21
+ properties.add_element 'brakeman:property', { 'brakeman:name' => 'number_of_controllers', 'brakeman:value' => tracker.controllers.length }
22
+ properties.add_element 'brakeman:property', { 'brakeman:name' => 'number_of_models', 'brakeman:value' => tracker.models.length - 1 }
23
+ properties.add_element 'brakeman:property', { 'brakeman:name' => 'ruby_version', 'brakeman:value' => number_of_templates(@tracker) }
24
+ properties.add_element 'brakeman:property', { 'brakeman:name' => 'number_of_templates', 'brakeman:value' => RUBY_VERSION }
25
+ properties.add_element 'brakeman:property', { 'brakeman:name' => 'brakeman_version', 'brakeman:value' => Brakeman::Version }
26
+
27
+ errors = test_suites.add_element 'brakeman:errors'
28
+ tracker.errors.each { |e|
29
+ error = errors.add_element 'brakeman:error'
30
+ error.add_attribute 'brakeman:message', e[:error]
31
+ e[:backtrace].each { |b|
32
+ backtrace = error.add_element 'brakeman:backtrace'
33
+ backtrace.add_text b
34
+ }
35
+ }
36
+
37
+ obsolete = test_suites.add_element 'brakeman:obsolete'
38
+ tracker.unused_fingerprints.each { |fingerprint|
39
+ obsolete.add_element 'brakeman:warning', { 'brakeman:fingerprint' => fingerprint }
40
+ }
41
+
42
+ ignored = test_suites.add_element 'brakeman:ignored'
43
+ ignored_warnings.each { |w|
44
+ warning = ignored.add_element 'brakeman:warning'
45
+ warning.add_attribute 'brakeman:message', w.message
46
+ warning.add_attribute 'brakeman:category', w.warning_type
47
+ warning.add_attribute 'brakeman:file', warning_file(w)
48
+ warning.add_attribute 'brakeman:line', w.line
49
+ warning.add_attribute 'brakeman:fingerprint', w.fingerprint
50
+ warning.add_attribute 'brakeman:confidence', TEXT_CONFIDENCE[w.confidence]
51
+ warning.add_attribute 'brakeman:code', w.format_code
52
+ warning.add_text w.to_s
53
+ }
54
+
55
+ hostname = `hostname`.strip
56
+ i = 0
57
+ all_warnings
58
+ .map { |warning| [warning.file, [warning]] }
59
+ .reduce({}) { |entries, entry|
60
+ key, value = entry
61
+ entries[key] = entries[key] ? entries[key].concat(value) : value
62
+ entries
63
+ }
64
+ .each { |file, warnings|
65
+ i += 1
66
+ test_suite = test_suites.add_element 'testsuite'
67
+ test_suite.add_attribute 'id', i
68
+ test_suite.add_attribute 'package', 'brakeman'
69
+ test_suite.add_attribute 'name', file.relative
70
+ test_suite.add_attribute 'timestamp', tracker.start_time.strftime('%FT%T')
71
+ test_suite.add_attribute 'hostname', hostname == '' ? 'localhost' : hostname
72
+ test_suite.add_attribute 'tests', checks.checks_run.length
73
+ test_suite.add_attribute 'failures', warnings.length
74
+ test_suite.add_attribute 'errors', '0'
75
+ test_suite.add_attribute 'time', '0'
76
+
77
+ test_suite.add_element 'properties'
78
+
79
+ warnings.each { |warning|
80
+ test_case = test_suite.add_element 'testcase'
81
+ test_case.add_attribute 'name', 'run_check'
82
+ test_case.add_attribute 'classname', warning.check
83
+ test_case.add_attribute 'time', '0'
84
+
85
+ failure = test_case.add_element 'failure'
86
+ failure.add_attribute 'message', warning.message
87
+ failure.add_attribute 'type', warning.warning_type
88
+ failure.add_attribute 'brakeman:fingerprint', warning.fingerprint
89
+ failure.add_attribute 'brakeman:file', warning_file(warning)
90
+ failure.add_attribute 'brakeman:line', warning.line
91
+ failure.add_attribute 'brakeman:confidence', TEXT_CONFIDENCE[warning.confidence]
92
+ failure.add_attribute 'brakeman:code', warning.format_code
93
+ failure.add_text warning.to_s
94
+ }
95
+
96
+ test_suite.add_element 'system-out'
97
+ test_suite.add_element 'system-err'
98
+ }
99
+
100
+ doc.add test_suites
101
+ doc.write io
102
+ io.string
103
+ end
104
+ end
@@ -84,7 +84,6 @@ class Brakeman::Report::Markdown < Brakeman::Report::Table
84
84
  end
85
85
 
86
86
  def convert_warning warning, original
87
- warning["Confidence"] = TEXT_CONFIDENCE[warning["Confidence"]]
88
87
  warning["Message"] = markdown_message original, warning["Message"]
89
88
  warning["Warning Type"] = "[#{warning['Warning Type']}](#{original.link})" if original.link
90
89
  warning
@@ -15,6 +15,7 @@ module Brakeman
15
15
  @escape_html = nil
16
16
  @erubis = nil
17
17
  @ruby_version = ""
18
+ @rails_version = nil
18
19
  end
19
20
 
20
21
  def default_protect_from_forgery?
@@ -1,3 +1,3 @@
1
1
  module Brakeman
2
- Version = "4.7.2"
2
+ Version = "4.8.0"
3
3
  end
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.7.2
4
+ version: 4.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Collins
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-25 00:00:00.000000000 Z
11
+ date: 2020-02-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -348,6 +348,7 @@ files:
348
348
  - lib/brakeman/report/report_hash.rb
349
349
  - lib/brakeman/report/report_html.rb
350
350
  - lib/brakeman/report/report_json.rb
351
+ - lib/brakeman/report/report_junit.rb
351
352
  - lib/brakeman/report/report_markdown.rb
352
353
  - lib/brakeman/report/report_table.rb
353
354
  - lib/brakeman/report/report_tabs.rb
@@ -405,7 +406,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
405
406
  - !ruby/object:Gem::Version
406
407
  version: '0'
407
408
  requirements: []
408
- rubygems_version: 3.0.3
409
+ rubygems_version: 3.1.2
409
410
  signing_key:
410
411
  specification_version: 4
411
412
  summary: Security vulnerability scanner for Ruby on Rails.