brakeman-lib 6.2.2 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
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