brakeman-lib 6.2.2 → 7.0.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: 43bb8b6197d17937fa07b7d6b69afaf5a0d4c0fad6332f2126a6f317e5e49262
4
- data.tar.gz: 29698bd867c9bfd2dc13baf27c463da77132be4f13d93fd1448428977241c9a2
3
+ metadata.gz: 1312162b44dd8b40558dcfb8cd9d5891ed37f7e46935f12d9890075de7ba41a6
4
+ data.tar.gz: 1ed9d4308f7c524aafca3d8e058886bc339a12ae9bd761377f57ae5ab2e423c8
5
5
  SHA512:
6
- metadata.gz: 8513e27561f8608fca48561c64ceaa42bf43042fee54d1b3102f1ad4dc855eb980c5cf963ed7cc48e8056d91b3d168553efab52f86c5064075b53aa49c1278af
7
- data.tar.gz: da6eef18fb43bfcb014f46e86cbd5df82c97b359f53abf18f263366940f45cb621bc85f1dc4933ac2319c063c963b2469c1eb936d26fec36e351c89577c822d9
6
+ metadata.gz: a3679861797e17b76407a24b80318a2395114461138bf9bbd13568dd1218498a34b0945f7f472b6388c09411dc22e9a3cba178bb6dd8cca93913f07c8b9323ed
7
+ data.tar.gz: de4bffcb73e376aeb00af8489954fe5b5620a7dc4459ed1501c0d665dd0ba7938d1b509f446a7780a487cf827b547a7c80ce07a7e34bc5423a2ac93e102a34e1
data/CHANGES.md CHANGED
@@ -1,3 +1,20 @@
1
+ # 7.0.0 - 2024-12-30
2
+
3
+ * Always warn about deserializing from Marshal
4
+ * Output `originalBaseUriIds` for SARIF format report
5
+ * Default to using Prism parser if available (disable with `--no-prism`)
6
+ * Update `terminal-table` version to use latest
7
+ * Update `eval` check to be a little noisier
8
+ * Fix array/hash unknown index handling
9
+ * Disable following symbolic links by default, re-enable with --follow-symlinks
10
+ * Add step (and timing) for finding files
11
+ * Add CSV library as explicit dependency for Ruby 3.4 support
12
+ * Major changes to how rescanning works
13
+ * Raise minimum Ruby version to 3.1
14
+ * Fix hardcoded globally excluded paths
15
+ * Remove updated entry in Brakeman ignore files (Toby Hsieh)
16
+ * Fix recursion when handling multiple assignment expressions
17
+
1
18
  # 6.2.2 - 2024-10-15
2
19
 
3
20
  * Ignore more native gems when building gem
@@ -22,6 +22,8 @@ module Brakeman
22
22
  init_options[:additional_libs_path] = options[:additional_libs_path]
23
23
  init_options[:engine_paths] = options[:engine_paths]
24
24
  init_options[:skip_vendor] = options[:skip_vendor]
25
+ init_options[:follow_symlinks] = options[:follow_symlinks]
26
+
25
27
  new(root, init_options)
26
28
  end
27
29
 
@@ -64,6 +66,7 @@ module Brakeman
64
66
  @absolute_engine_paths = @engine_paths.select { |path| path.start_with?(File::SEPARATOR) }
65
67
  @relative_engine_paths = @engine_paths - @absolute_engine_paths
66
68
  @skip_vendor = init_options[:skip_vendor]
69
+ @follow_symlinks = init_options[:follow_symlinks]
67
70
  @gemspec = nil
68
71
  @root_search_pattern = nil
69
72
  end
@@ -161,21 +164,26 @@ module Brakeman
161
164
  end
162
165
 
163
166
  def glob_files(directory, name, extensions = ".rb")
164
- root_directory = "#{root_search_pattern}#{directory}"
165
- patterns = ["#{root_directory}/**/#{name}#{extensions}"]
166
-
167
- Dir.glob("#{root_directory}/**/*", File::FNM_DOTMATCH).each do |path|
168
- if File.symlink?(path) && File.directory?(path)
169
- symlink_target = File.readlink(path)
170
- if Pathname.new(symlink_target).relative?
171
- symlink_target = File.join(File.dirname(path), symlink_target)
167
+ if @follow_symlinks
168
+ root_directory = "#{root_search_pattern}#{directory}"
169
+ patterns = ["#{root_directory}/**/#{name}#{extensions}"]
170
+
171
+ Dir.glob("#{root_directory}/**/*", File::FNM_DOTMATCH).each do |path|
172
+ if File.symlink?(path) && File.directory?(path)
173
+ symlink_target = File.readlink(path)
174
+ if Pathname.new(symlink_target).relative?
175
+ symlink_target = File.join(File.dirname(path), symlink_target)
176
+ end
177
+ patterns << "#{search_pattern(symlink_target)}/**/#{name}#{extensions}"
172
178
  end
173
- patterns << "#{search_pattern(symlink_target)}/**/#{name}#{extensions}"
174
179
  end
175
- end
176
180
 
177
- files = patterns.flat_map { |pattern| Dir.glob(pattern) }
178
- files.uniq
181
+ files = patterns.flat_map { |pattern| Dir.glob(pattern) }
182
+ files.uniq
183
+ else
184
+ pattern = "#{root_search_pattern}#{directory}/**/#{name}#{extensions}"
185
+ Dir.glob(pattern)
186
+ end
179
187
  end
180
188
 
181
189
  def select_files(paths)
@@ -201,15 +209,14 @@ module Brakeman
201
209
  end
202
210
  end
203
211
 
204
- EXCLUDED_PATHS = %w[
205
- /generators/
212
+ EXCLUDED_PATHS = regex_for_paths %w[
213
+ generators/
206
214
  lib/tasks/
207
215
  lib/templates/
208
216
  db/
209
217
  spec/
210
218
  test/
211
219
  tmp/
212
- log/
213
220
  ]
214
221
 
215
222
  def reject_global_excludes(paths)
@@ -219,9 +226,7 @@ module Brakeman
219
226
  if @skip_vendor and relative_path.include? 'vendor/' and !in_engine_paths?(path) and !in_add_libs_paths?(path)
220
227
  true
221
228
  else
222
- EXCLUDED_PATHS.any? do |excluded|
223
- relative_path.include? excluded
224
- end
229
+ match_path EXCLUDED_PATHS, path
225
230
  end
226
231
  end
227
232
  end
@@ -76,10 +76,13 @@ class Brakeman::CheckDeserialize < Brakeman::BaseCheck
76
76
  confidence = :high
77
77
  elsif input = include_user_input?(arg)
78
78
  confidence = :medium
79
+ elsif target == :Marshal
80
+ confidence = :low
81
+ message = msg("Use of ", msg_code("#{target}.#{method}"), " may be dangerous")
79
82
  end
80
83
 
81
84
  if confidence
82
- message = msg(msg_code("#{target}.#{method}"), " called with ", msg_input(input))
85
+ message ||= msg(msg_code("#{target}.#{method}"), " called with ", msg_input(input))
83
86
 
84
87
  warn :result => result,
85
88
  :warning_type => "Remote Code Execution",
@@ -23,13 +23,31 @@ class Brakeman::CheckEvaluation < Brakeman::BaseCheck
23
23
  return unless original? result
24
24
 
25
25
  if input = include_user_input?(result[:call].arglist)
26
+ confidence = :high
27
+ message = msg(msg_input(input), " evaluated as code")
28
+ elsif string_evaluation? result[:call].first_arg
29
+ confidence = :low
30
+ message = "Dynamic string evaluated as code"
31
+ elsif safe_literal? result[:call].first_arg
32
+ # don't warn
33
+ elsif result[:call].method == :eval
34
+ confidence = :low
35
+ message = "Dynamic code evaluation"
36
+ end
37
+
38
+ if confidence
26
39
  warn :result => result,
27
40
  :warning_type => "Dangerous Eval",
28
41
  :warning_code => :code_eval,
29
- :message => "User input in eval",
42
+ :message => message,
30
43
  :user_input => input,
31
- :confidence => :high,
44
+ :confidence => confidence,
32
45
  :cwe_id => [913, 95]
33
46
  end
34
47
  end
48
+
49
+ def string_evaluation? exp
50
+ string_interp? exp or
51
+ (call? exp and string? exp.target)
52
+ end
35
53
  end
@@ -33,6 +33,7 @@ class Brakeman::CheckModelAttrAccessible < Brakeman::BaseCheck
33
33
  :confidence => confidence,
34
34
  :code => Sexp.new(:lit, attribute),
35
35
  :cwe_id => [915]
36
+
36
37
  break # Prevent from matching single attr multiple times
37
38
  end
38
39
  end
@@ -13,8 +13,9 @@ module Brakeman
13
13
  if @use_prism
14
14
  begin
15
15
  require 'prism'
16
+ Brakeman.debug '[Notice] Using Prism parser'
16
17
  rescue LoadError => e
17
- Brakeman.debug "Asked to use Prism, but failed to load: #{e}"
18
+ Brakeman.debug "[Notice] Asked to use Prism, but failed to load: #{e}"
18
19
  @use_prism = false
19
20
  end
20
21
  end
@@ -161,14 +161,13 @@ module Brakeman::Options
161
161
 
162
162
  opts.on "--[no-]prism", "Use the Prism parser" do |use_prism|
163
163
  if use_prism
164
- prism_version = '0.30'
164
+ min_prism_version = '1.0.0'
165
165
 
166
166
  begin
167
- # Specifying minimum version here,
168
- # since it can't be in the gem dependency list because it is optional
169
- gem 'prism', "~>#{prism_version}"
167
+ gem 'prism', ">=#{min_prism_version}"
168
+ require 'prism'
170
169
  rescue Gem::MissingSpecVersionError, Gem::MissingSpecError, Gem::LoadError => e
171
- $stderr.puts "Please install `prism` version #{prism_version} or newer:"
170
+ $stderr.puts "Please install `prism` version #{min_prism_version} or newer:"
172
171
  raise e
173
172
  end
174
173
  end
@@ -223,6 +222,10 @@ module Brakeman::Options
223
222
  options[:engine_paths].merge paths
224
223
  end
225
224
 
225
+ opts.on '--[no-]follow-symlinks', 'Follow symbolic links for directions' do |follow_symlinks|
226
+ options[:follow_symlinks] = follow_symlinks
227
+ end
228
+
226
229
  opts.on "-E", "--enable Check1,Check2,etc", Array, "Enable the specified checks" do |checks|
227
230
  checks.map! do |check|
228
231
  if check.start_with? "Check"
@@ -97,6 +97,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
97
97
  end
98
98
 
99
99
  def process_bracket_call exp
100
+ # TODO: What is even happening in this method?
100
101
  r = replace(exp)
101
102
 
102
103
  if r != exp
@@ -127,7 +128,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
127
128
  return r
128
129
  end
129
130
  else
130
- t = nil
131
+ t = exp.target # put it back?
131
132
  end
132
133
 
133
134
  if hash? t
@@ -242,6 +243,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
242
243
  exp = math_op(method, target, first_arg, exp)
243
244
  end
244
245
  when :[]
246
+ # TODO: This might never be used because of process_bracket_call above
245
247
  if array? target
246
248
  exp = process_array_access(target, exp.args, exp)
247
249
  elsif hash? target
@@ -666,7 +668,9 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
666
668
  end
667
669
 
668
670
  unless array? exp[1] and array? exp[2]
669
- return process_default(exp)
671
+ # Already processed RHS, don't do it again
672
+ # https://github.com/presidentbeef/brakeman/issues/1877
673
+ return exp
670
674
  end
671
675
 
672
676
  vars = exp[1].dup
@@ -13,7 +13,7 @@ module Brakeman
13
13
  @file_type = guess_from_path(file.path.relative)
14
14
  end
15
15
 
16
- @file_type || :libs
16
+ @file_type || :lib
17
17
  end
18
18
 
19
19
  MODEL_CLASSES = [
@@ -26,10 +26,10 @@ module Brakeman
26
26
  parent = class_name(exp.parent_name)
27
27
 
28
28
  if name.match(/Controller$/)
29
- @file_type = :controllers
29
+ @file_type = :controller
30
30
  return exp
31
31
  elsif MODEL_CLASSES.include? parent
32
- @file_type = :models
32
+ @file_type = :model
33
33
  return exp
34
34
  end
35
35
 
@@ -39,19 +39,21 @@ module Brakeman
39
39
  def guess_from_path path
40
40
  case
41
41
  when path.include?('app/models')
42
- :models
42
+ :model
43
43
  when path.include?('app/controllers')
44
- :controllers
44
+ :controller
45
45
  when path.include?('config/initializers')
46
- :initializers
46
+ :initializer
47
47
  when path.include?('lib/')
48
- :libs
48
+ :lib
49
49
  when path.match?(%r{config/environments/(?!production\.rb)$})
50
50
  :skip
51
51
  when path.match?(%r{environments/production\.rb$})
52
52
  :skip
53
53
  when path.match?(%r{application\.rb$})
54
54
  :skip
55
+ when path.match?(%r{config/routes\.rb$})
56
+ :skip
55
57
  end
56
58
  end
57
59
 
@@ -130,7 +130,6 @@ module Brakeman
130
130
 
131
131
  output = {
132
132
  :ignored_warnings => warnings,
133
- :updated => Time.now.to_s,
134
133
  :brakeman_version => Brakeman::Version
135
134
  }
136
135
 
@@ -1,8 +1,10 @@
1
+ require 'uri'
2
+
1
3
  class Brakeman::Report::SARIF < Brakeman::Report::Base
2
4
  def generate_report
3
5
  sarif_log = {
4
6
  :version => '2.1.0',
5
- :$schema => 'https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json',
7
+ :$schema => 'https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0.json',
6
8
  :runs => runs,
7
9
  }
8
10
  JSON.pretty_generate sarif_log
@@ -20,10 +22,122 @@ class Brakeman::Report::SARIF < Brakeman::Report::Base
20
22
  },
21
23
  },
22
24
  :results => results,
23
- },
25
+ }.merge(original_uri_base_ids)
24
26
  ]
25
27
  end
26
28
 
29
+ # Output base URIs
30
+ # based on what the user specified for the application path
31
+ # and whether or not --absolute-paths was set.
32
+ def original_uri_base_ids
33
+ if tracker.options[:app_path] == '.'
34
+ # Probably no app_path was specified, as that's the default
35
+
36
+ if absolute_paths?
37
+ # Set %SRCROOT% to absolute path
38
+ {
39
+ originalUriBaseIds: {
40
+ '%SRCROOT%' => {
41
+ uri: file_uri(tracker.app_tree.root),
42
+ description: {
43
+ text: 'Base path for application'
44
+ }
45
+ }
46
+ }
47
+ }
48
+ else
49
+ # Empty %SRCROOT%
50
+ # This avoids any paths appearing in the report
51
+ # that are not part of the application directory.
52
+ # Seems fine!
53
+ {
54
+ originalUriBaseIds: {
55
+ '%SRCROOT%' => {
56
+ description: {
57
+ text: 'Base path for application'
58
+ }
59
+ },
60
+ }
61
+ }
62
+
63
+ end
64
+ elsif tracker.options[:app_path] != tracker.app_tree.root
65
+ # Path was specified and it was relative
66
+
67
+ if absolute_paths?
68
+ # Include absolute root and relative application path
69
+ {
70
+ originalUriBaseIds: {
71
+ PROJECTROOT: {
72
+ uri: file_uri(tracker.app_tree.root),
73
+ description: {
74
+ text: 'Base path for all project files'
75
+ }
76
+ },
77
+ '%SRCROOT%' => {
78
+ # Technically should ensure this doesn't have any '..'
79
+ # but... TODO
80
+ uri: File.join(tracker.options[:app_path], '/'),
81
+ uriBaseId: 'PROJECTROOT',
82
+ description: {
83
+ text: 'Base path for application'
84
+ }
85
+ }
86
+ }
87
+ }
88
+ else
89
+ # Just include relative application path.
90
+ # Not clear this is 100% valid, but there is one example in the spec like this
91
+ {
92
+ originalUriBaseIds: {
93
+ PROJECTROOT: {
94
+ description: {
95
+ text: 'Base path for all project files'
96
+ }
97
+ },
98
+ '%SRCROOT%' => {
99
+ # Technically should ensure this doesn't have any '..'
100
+ # but... TODO
101
+ uri: File.join(tracker.options[:app_path], '/'),
102
+ uriBaseId: 'PROJECTROOT',
103
+ description: {
104
+ text: 'Base path for application'
105
+ }
106
+ }
107
+ }
108
+ }
109
+ end
110
+ else
111
+ # app_path was absolute
112
+
113
+ if absolute_paths?
114
+ # Set %SRCROOT% to absolute path
115
+ {
116
+ originalUriBaseIds: {
117
+ '%SRCROOT%' => {
118
+ uri: file_uri(tracker.app_tree.root),
119
+ description: {
120
+ text: 'Base path for application'
121
+ }
122
+ }
123
+ }
124
+ }
125
+ else
126
+ # Empty %SRCROOT%
127
+ # Seems fine!
128
+ {
129
+ originalUriBaseIds: {
130
+ '%SRCROOT%' => {
131
+ description: {
132
+ text: 'Base path for application'
133
+ }
134
+ },
135
+ }
136
+ }
137
+ end
138
+ end
139
+ end
140
+
27
141
  def rules
28
142
  @rules ||= unique_warnings_by_warning_code.map do |warning|
29
143
  rule_id = render_id warning
@@ -130,4 +244,10 @@ class Brakeman::Report::SARIF < Brakeman::Report::Base
130
244
  })
131
245
  @@levels_from_confidence[warning.confidence]
132
246
  end
247
+
248
+ # File URI as a string with trailing forward-slash
249
+ # as required by SARIF standard
250
+ def file_uri(path)
251
+ URI::File.build(path: File.join(path, '/')).to_s
252
+ end
133
253
  end