brakeman-min 6.2.2 → 7.0.1

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: c7523d5074d9d9352e585ad32ec90148c7a66452610d27bc29b7de5fe6770df9
4
- data.tar.gz: db9859643a7a8ed60a3bb721614fc4a8a2cf2910dcfb5e4b89b32bf20e2af75e
3
+ metadata.gz: baa16f18d1ceaf0c04654b5b84547e3bb0cd23368409b980742df987f88df059
4
+ data.tar.gz: 90b31d54feaad0b87250d7e7599c8bcbe4d290a4bca3132e4e60ea2b83af6e25
5
5
  SHA512:
6
- metadata.gz: 536df54884f0d9b9dba43be944335119b4fb75cd4096b69cdcec15b1058cb039985cd8923542f5d0df667d5609813fac1c1a0f511ee8169d4f0771bebb3be5b0
7
- data.tar.gz: 782ec2ca43bb670743915f255510182bc6399bae367aba68cdad17508916b16abddda1bd6d5874c168d690ef67909495e25289a680a69fb921d9a4dcffdb6a2d
6
+ metadata.gz: ac5c7a446bd842ccf61292ac59505b95f9fc5840ef5749f08f39ce9c4a905d8bf24045377b1ccb035d0f3b5810984c9564d962d1ba27da89f0129044e522f81d
7
+ data.tar.gz: 54e8dd54503821cb93b9c11aa6e69aaec2da4ba81308d507df9721fe50e2f21cb2124a8d2f46b9e907696f642c137dc05d8d51bcc4d8fb8e794b51b30b186b6d
data/CHANGES.md CHANGED
@@ -1,3 +1,29 @@
1
+ # 7.0.1 - 2025-04-03
2
+
3
+ * Avoid warning on evaluation of plain strings
4
+ * Enable use of custom/alternative Gemfiles
5
+ * Fix error on directory with `rb` extension (viralpraxis)
6
+ * Support `terminal-table` 4.0 (Chedli Bourguiba)
7
+ * Better support Prism 1.4.0
8
+ * Only output timing for each file when using `--debug`
9
+
10
+ # 7.0.0 - 2024-12-30
11
+
12
+ * Always warn about deserializing from Marshal
13
+ * Output `originalBaseUriIds` for SARIF format report
14
+ * Default to using Prism parser if available (disable with `--no-prism`)
15
+ * Update `terminal-table` version to use latest
16
+ * Update `eval` check to be a little noisier
17
+ * Fix array/hash unknown index handling
18
+ * Disable following symbolic links by default, re-enable with --follow-symlinks
19
+ * Add step (and timing) for finding files
20
+ * Add CSV library as explicit dependency for Ruby 3.4 support
21
+ * Major changes to how rescanning works
22
+ * Raise minimum Ruby version to 3.1
23
+ * Fix hardcoded globally excluded paths
24
+ * Remove updated entry in Brakeman ignore files (Toby Hsieh)
25
+ * Fix recursion when handling multiple assignment expressions
26
+
1
27
  # 6.2.2 - 2024-10-15
2
28
 
3
29
  * Ignore more native gems when building gem
data/README.md CHANGED
@@ -63,7 +63,7 @@ Outside of Rails root (note that the output file is relative to path/to/rails/ap
63
63
 
64
64
  # Compatibility
65
65
 
66
- Brakeman should work with any version of Rails from 2.3.x to 7.x.
66
+ Brakeman should work with any version of Rails from 2.3.x to 8.x.
67
67
 
68
68
  Brakeman can analyze code written with Ruby 2.0 syntax and newer, but requires at least Ruby 3.0.0 to run.
69
69
 
@@ -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,28 +164,38 @@ 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)
182
190
  paths = select_only_files(paths)
183
191
  paths = reject_skipped_files(paths)
184
192
  paths = convert_to_file_paths(paths)
185
- reject_global_excludes(paths)
193
+ paths = reject_global_excludes(paths)
194
+ reject_directories(paths)
195
+ end
196
+
197
+ def reject_directories(paths)
198
+ paths.reject { |path| File.directory?(path) }
186
199
  end
187
200
 
188
201
  def select_only_files(paths)
@@ -201,15 +214,14 @@ module Brakeman
201
214
  end
202
215
  end
203
216
 
204
- EXCLUDED_PATHS = %w[
205
- /generators/
217
+ EXCLUDED_PATHS = regex_for_paths %w[
218
+ generators/
206
219
  lib/tasks/
207
220
  lib/templates/
208
221
  db/
209
222
  spec/
210
223
  test/
211
224
  tmp/
212
- log/
213
225
  ]
214
226
 
215
227
  def reject_global_excludes(paths)
@@ -219,9 +231,7 @@ module Brakeman
219
231
  if @skip_vendor and relative_path.include? 'vendor/' and !in_engine_paths?(path) and !in_add_libs_paths?(path)
220
232
  true
221
233
  else
222
- EXCLUDED_PATHS.any? do |excluded|
223
- relative_path.include? excluded
224
- end
234
+ match_path EXCLUDED_PATHS, path
225
235
  end
226
236
  end
227
237
  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",
@@ -22,14 +22,51 @@ class Brakeman::CheckEvaluation < Brakeman::BaseCheck
22
22
  def process_result result
23
23
  return unless original? result
24
24
 
25
- if input = include_user_input?(result[:call].arglist)
26
- warn :result => result,
27
- :warning_type => "Dangerous Eval",
28
- :warning_code => :code_eval,
29
- :message => "User input in eval",
30
- :user_input => input,
31
- :confidence => :high,
32
- :cwe_id => [913, 95]
25
+ first_arg = result[:call].first_arg
26
+
27
+ unless safe_value? first_arg
28
+ if input = include_user_input?(first_arg)
29
+ confidence = :high
30
+ message = msg(msg_input(input), " evaluated as code")
31
+ elsif string_evaluation? first_arg
32
+ confidence = :low
33
+ message = "Dynamic string evaluated as code"
34
+ elsif result[:call].method == :eval
35
+ confidence = :low
36
+ message = "Dynamic code evaluation"
37
+ end
38
+
39
+ if confidence
40
+ warn :result => result,
41
+ :warning_type => "Dangerous Eval",
42
+ :warning_code => :code_eval,
43
+ :message => message,
44
+ :user_input => input,
45
+ :confidence => confidence,
46
+ :cwe_id => [913, 95]
47
+ end
48
+ end
49
+ end
50
+
51
+ def string_evaluation? exp
52
+ string_interp? exp or
53
+ (call? exp and string? exp.target)
54
+ end
55
+
56
+ def safe_value? exp
57
+ return true unless sexp? exp
58
+
59
+ case exp.sexp_type
60
+ when :dstr
61
+ exp.all? { |e| safe_value? e}
62
+ when :evstr
63
+ safe_value? exp.value
64
+ when :str, :lit
65
+ true
66
+ when :call
67
+ always_safe_method? exp.method
68
+ else
69
+ false
33
70
  end
34
71
  end
35
72
  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
@@ -87,7 +87,7 @@ class Brakeman::CheckWeakRSAKey < Brakeman::BaseCheck
87
87
 
88
88
  if string? padding_arg
89
89
  padding_arg = padding_arg.deep_clone(padding_arg.line)
90
- padding_arg.value.downcase!
90
+ padding_arg.value = padding_arg.value.downcase
91
91
  end
92
92
 
93
93
  case padding_arg
@@ -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,14 @@ 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
+
229
+ opts.on '--gemfile GEMFILE', 'Specify Gemfile to scan' do |gemfile|
230
+ options[:gemfile] = gemfile
231
+ end
232
+
226
233
  opts.on "-E", "--enable Check1,Check2,etc", Array, "Enable the specified checks" do |checks|
227
234
  checks.map! do |check|
228
235
  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
@@ -268,7 +270,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
268
270
  end
269
271
  when :<<
270
272
  if string? target and string? first_arg
271
- target.value << first_arg.value
273
+ target.value += first_arg.value
272
274
  env[target_var] = target
273
275
  return target
274
276
  elsif string? target and string_interp? first_arg
@@ -276,8 +278,9 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
276
278
  env[target_var] = exp
277
279
  elsif string? first_arg and string_interp? target
278
280
  if string? target.last
279
- target.last.value << first_arg.value
281
+ target.last.value += first_arg.value
280
282
  elsif target.last.is_a? String
283
+ # TODO Use target.last += ?
281
284
  target.last << first_arg.value
282
285
  else
283
286
  target << first_arg
@@ -666,7 +669,9 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
666
669
  end
667
670
 
668
671
  unless array? exp[1] and array? exp[2]
669
- return process_default(exp)
672
+ # Already processed RHS, don't do it again
673
+ # https://github.com/presidentbeef/brakeman/issues/1877
674
+ return exp
670
675
  end
671
676
 
672
677
  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