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 +4 -4
- data/CHANGES.md +17 -0
- data/lib/brakeman/app_tree.rb +23 -18
- data/lib/brakeman/checks/check_deserialize.rb +4 -1
- data/lib/brakeman/checks/check_evaluation.rb +20 -2
- data/lib/brakeman/checks/check_model_attr_accessible.rb +1 -0
- data/lib/brakeman/file_parser.rb +2 -1
- data/lib/brakeman/options.rb +8 -5
- data/lib/brakeman/processors/alias_processor.rb +6 -2
- data/lib/brakeman/processors/lib/file_type_detector.rb +9 -7
- data/lib/brakeman/report/ignore/config.rb +0 -1
- data/lib/brakeman/report/report_sarif.rb +122 -2
- data/lib/brakeman/rescanner.rb +40 -390
- data/lib/brakeman/scanner.rb +62 -38
- data/lib/brakeman/tracker/file_cache.rb +83 -0
- data/lib/brakeman/tracker.rb +19 -2
- data/lib/brakeman/version.rb +1 -1
- data/lib/brakeman.rb +12 -2
- metadata +36 -38
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1312162b44dd8b40558dcfb8cd9d5891ed37f7e46935f12d9890075de7ba41a6
|
4
|
+
data.tar.gz: 1ed9d4308f7c524aafca3d8e058886bc339a12ae9bd761377f57ae5ab2e423c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/brakeman/app_tree.rb
CHANGED
@@ -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
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
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
|
-
|
178
|
-
|
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
|
-
|
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
|
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
|
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 =>
|
42
|
+
:message => message,
|
30
43
|
:user_input => input,
|
31
|
-
:confidence =>
|
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
|
data/lib/brakeman/file_parser.rb
CHANGED
@@ -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
|
data/lib/brakeman/options.rb
CHANGED
@@ -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
|
-
|
164
|
+
min_prism_version = '1.0.0'
|
165
165
|
|
166
166
|
begin
|
167
|
-
|
168
|
-
|
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 #{
|
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 =
|
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
|
-
|
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 || :
|
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 = :
|
29
|
+
@file_type = :controller
|
30
30
|
return exp
|
31
31
|
elsif MODEL_CLASSES.include? parent
|
32
|
-
@file_type = :
|
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
|
-
:
|
42
|
+
:model
|
43
43
|
when path.include?('app/controllers')
|
44
|
-
:
|
44
|
+
:controller
|
45
45
|
when path.include?('config/initializers')
|
46
|
-
:
|
46
|
+
:initializer
|
47
47
|
when path.include?('lib/')
|
48
|
-
:
|
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
|
|
@@ -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
|
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
|