brakeman-lib 8.0.5 → 8.1.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 +19 -0
- data/lib/brakeman/app_tree.rb +14 -3
- data/lib/brakeman/checks/check_eol_rails.rb +2 -1
- data/lib/brakeman/checks/check_eol_ruby.rb +1 -0
- data/lib/brakeman/checks/check_execute.rb +18 -23
- data/lib/brakeman/checks/check_validation_regex.rb +3 -2
- data/lib/brakeman/options.rb +2 -2
- data/lib/brakeman/parsers/template_parser.rb +9 -2
- data/lib/brakeman/processors/haml_template_processor.rb +1 -1
- data/lib/brakeman/processors/model_processor.rb +18 -5
- data/lib/brakeman/report/report_sonar.rb +53 -19
- data/lib/brakeman/version.rb +1 -1
- data/lib/brakeman/warning.rb +1 -2
- data/lib/brakeman.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d76a4590b3df146050c07df100680cc1a72821ca5c3e3e6bdf50de904603529a
|
|
4
|
+
data.tar.gz: 03de5ab61f3f56d0f99e6d6c833b671ddda0c6721214853cc3ddaa909fa01e5d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 39fb331fc88e7a4360eac71cebdca9aebc8a3082ee6293ed52ae1b5d419e5685a7eddb1eccf5f80db7914f8cd2aefc2738f6b020d2f9a55bb6d8c9a5a2938c91
|
|
7
|
+
data.tar.gz: 4adf39f28d7a5e8ddc64f3e4d539bde4b0dd77f482be347601d4e2cf4846da63cfe37046a7ca66c1ebadcd6156bc2b2726c47b54f7bc0bdacbf5d0d70f044456
|
data/CHANGES.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
# 8.1.0 - 2026-09-30
|
|
2
|
+
|
|
3
|
+
* Better help and error message for `--ensure-latest`
|
|
4
|
+
* Fix frozen string error (Shai Coleman)
|
|
5
|
+
* Update Sonar report format (fangxing)
|
|
6
|
+
* Fix frozen `src` string error
|
|
7
|
+
* Recognize `Haml::AttributeBuilder.build_class` as an escaped output (Yuriy Tumanov)
|
|
8
|
+
* Include regex code in validation warnings (Eliot Sykes)
|
|
9
|
+
* Check validation regexes in non-activerecord models (Eliot Sykes)
|
|
10
|
+
* Skip top-level vendor directory before recursive globbing (Conor O'Donnell)
|
|
11
|
+
* Support Rails 7.1+ positional enum syntax in SQL injection check (Michael Vogl/Yuriy Tumanov)
|
|
12
|
+
|
|
13
|
+
# 8.0.6 - 2026-08-13
|
|
14
|
+
|
|
15
|
+
* Fix EOL date for Rails 8.0 (yeaseul-kim)
|
|
16
|
+
* Add EOL dates for Rails 8.1 and Ruby 4.0
|
|
17
|
+
* Fix command injection false positives (Jacob Evelyn)
|
|
18
|
+
* Fix unused variable warning (viralpraxis)
|
|
19
|
+
|
|
1
20
|
# 8.0.5 - 2026-06-12
|
|
2
21
|
|
|
3
22
|
* Add `quote_schema_name` to safe quote method list (Zsolt Kozaroczy)
|
data/lib/brakeman/app_tree.rb
CHANGED
|
@@ -64,6 +64,8 @@ module Brakeman
|
|
|
64
64
|
@only_files = init_options[:only_files]
|
|
65
65
|
@additional_libs_path = init_options[:additional_libs_path] || []
|
|
66
66
|
@engine_paths = init_options[:engine_paths] || []
|
|
67
|
+
@additional_libs_path_pairs = @additional_libs_path.map { |p| [p, expand_path(p)] }
|
|
68
|
+
@engine_path_pairs = @engine_paths.map { |p| [p, expand_path(p)] }
|
|
67
69
|
@absolute_engine_paths = @engine_paths.select { |path| path.start_with?(File::SEPARATOR) }
|
|
68
70
|
@relative_engine_paths = @engine_paths - @absolute_engine_paths
|
|
69
71
|
@skip_vendor = init_options[:skip_vendor]
|
|
@@ -251,7 +253,10 @@ module Brakeman
|
|
|
251
253
|
paths.reject do |path|
|
|
252
254
|
relative_path = path.relative
|
|
253
255
|
|
|
254
|
-
if @skip_vendor and
|
|
256
|
+
if @skip_vendor and
|
|
257
|
+
(relative_path == 'vendor' || relative_path.include?('vendor/')) and
|
|
258
|
+
!in_engine_paths?(path) and
|
|
259
|
+
!in_add_libs_paths?(path)
|
|
255
260
|
true
|
|
256
261
|
else
|
|
257
262
|
match_path EXCLUDED_PATHS, path
|
|
@@ -260,11 +265,17 @@ module Brakeman
|
|
|
260
265
|
end
|
|
261
266
|
|
|
262
267
|
def in_engine_paths?(path)
|
|
263
|
-
@
|
|
268
|
+
@engine_path_pairs.any? do |original, expanded|
|
|
269
|
+
path.absolute.include?(original) ||
|
|
270
|
+
expanded.start_with?("#{path.absolute}#{File::SEPARATOR}")
|
|
271
|
+
end
|
|
264
272
|
end
|
|
265
273
|
|
|
266
274
|
def in_add_libs_paths?(path)
|
|
267
|
-
@
|
|
275
|
+
@additional_libs_path_pairs.any? do |original, expanded|
|
|
276
|
+
path.absolute.include?(original) ||
|
|
277
|
+
expanded.start_with?("#{path.absolute}#{File::SEPARATOR}")
|
|
278
|
+
end
|
|
268
279
|
end
|
|
269
280
|
|
|
270
281
|
def match_path files, path
|
|
@@ -25,6 +25,7 @@ class Brakeman::CheckEOLRails < Brakeman::EOLCheck
|
|
|
25
25
|
['7.0.0', '7.0.99'] => Date.new(2025, 4, 1),
|
|
26
26
|
['7.1.0', '7.1.99'] => Date.new(2025, 10, 1),
|
|
27
27
|
['7.2.0', '7.2.99'] => Date.new(2026, 8, 9),
|
|
28
|
-
['8.0.0', '8.0.99'] => Date.new(2026,
|
|
28
|
+
['8.0.0', '8.0.99'] => Date.new(2026, 11, 7),
|
|
29
|
+
['8.1.0', '8.1.99'] => Date.new(2027, 10, 10),
|
|
29
30
|
}
|
|
30
31
|
end
|
|
@@ -55,6 +55,13 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
|
|
|
55
55
|
first_arg = call.first_arg
|
|
56
56
|
failure = nil
|
|
57
57
|
|
|
58
|
+
# All spawn-family methods optionally accept an env hash as the first
|
|
59
|
+
# argument. Strip it so first_arg always points to the command name.
|
|
60
|
+
if hash?(first_arg)
|
|
61
|
+
args.delete_at(1)
|
|
62
|
+
first_arg = args[1]
|
|
63
|
+
end
|
|
64
|
+
|
|
58
65
|
case call.method
|
|
59
66
|
when :popen
|
|
60
67
|
# Normally, if we're in a `popen` call, we only are worried about shell
|
|
@@ -99,14 +106,17 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
|
|
|
99
106
|
|
|
100
107
|
break if failure
|
|
101
108
|
end
|
|
102
|
-
when :system, :exec
|
|
103
|
-
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
#
|
|
108
|
-
#
|
|
109
|
-
|
|
109
|
+
when :system, :exec, :spawn, :capture2, :capture2e, :capture3,
|
|
110
|
+
:popen2, :popen2e, :popen3
|
|
111
|
+
# Comma-separated arguments are passed directly to execve without a
|
|
112
|
+
# shell, so only the first argument (the command name) needs checking.
|
|
113
|
+
# Exception: `bash -c <script>` — the script is the injection vector.
|
|
114
|
+
#
|
|
115
|
+
# All of these methods accept an optional trailing options hash
|
|
116
|
+
# (e.g. stdin_data:, chdir:); strip it before checking.
|
|
117
|
+
args.pop if hash?(args.last) && args.length > 2
|
|
118
|
+
|
|
119
|
+
if dash_c_shell_command?(first_arg, args[2])
|
|
110
120
|
failure = include_user_input?(args[3]) ||
|
|
111
121
|
dangerous_interp?(args[3]) ||
|
|
112
122
|
dangerous_string_building?(args[3])
|
|
@@ -115,21 +125,6 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
|
|
|
115
125
|
dangerous_interp?(first_arg) ||
|
|
116
126
|
dangerous_string_building?(first_arg)
|
|
117
127
|
end
|
|
118
|
-
when :capture2, :capture2e, :capture3
|
|
119
|
-
# Open3 capture methods can take a :stdin_data argument which is used as the
|
|
120
|
-
# the input to the called command so it is not succeptable to command injection.
|
|
121
|
-
# As such if the last argument is a hash (and therefore execution options) it
|
|
122
|
-
# should be ignored
|
|
123
|
-
|
|
124
|
-
args.pop if hash?(args.last) && args.length > 2
|
|
125
|
-
|
|
126
|
-
args.each_sexp do |arg|
|
|
127
|
-
failure = include_user_input?(arg) ||
|
|
128
|
-
dangerous_interp?(arg) ||
|
|
129
|
-
dangerous_string_building?(arg)
|
|
130
|
-
|
|
131
|
-
break if failure
|
|
132
|
-
end
|
|
133
128
|
else
|
|
134
129
|
failure = include_user_input?(args) ||
|
|
135
130
|
dangerous_interp?(args) ||
|
|
@@ -16,7 +16,7 @@ class Brakeman::CheckValidationRegex < Brakeman::BaseCheck
|
|
|
16
16
|
FORMAT = Sexp.new(:lit, :format)
|
|
17
17
|
|
|
18
18
|
def run_check
|
|
19
|
-
|
|
19
|
+
tracker.models.each do |name, model|
|
|
20
20
|
@current_model = model
|
|
21
21
|
format_validations = model.options[:validates_format_of]
|
|
22
22
|
|
|
@@ -92,7 +92,8 @@ class Brakeman::CheckValidationRegex < Brakeman::BaseCheck
|
|
|
92
92
|
:message => msg("Insufficient validation for ", msg_code(get_name validator), " using ", msg_code(regex.inspect), ". Use ", msg_code("\\A"), " and ", msg_code("\\z"), " as anchors"),
|
|
93
93
|
:line => value.line,
|
|
94
94
|
:confidence => :high,
|
|
95
|
-
:cwe_id => [777]
|
|
95
|
+
:cwe_id => [777],
|
|
96
|
+
:code => validator
|
|
96
97
|
end
|
|
97
98
|
end
|
|
98
99
|
|
data/lib/brakeman/options.rb
CHANGED
|
@@ -63,9 +63,9 @@ module Brakeman::Options
|
|
|
63
63
|
options[:exit_on_error] = exit_on_error
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
opts.on "--ensure-latest [DAYS]", Integer, "Fail when Brakeman is outdated. Optionally set minimum age in days." do |days|
|
|
66
|
+
opts.on "--ensure-latest [DAYS]", Integer, "Fail when Brakeman is outdated. Optionally set minimum age in days (1-15)." do |days|
|
|
67
67
|
if days and not (1..15).include? days
|
|
68
|
-
raise OptionParser::InvalidArgument
|
|
68
|
+
raise OptionParser::InvalidArgument, "Minimum age must be 1-15 days."
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
options[:ensure_latest] = days || true
|
|
@@ -56,8 +56,15 @@ module Brakeman
|
|
|
56
56
|
else
|
|
57
57
|
ERB.new(text, nil, '-').src
|
|
58
58
|
end
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
|
|
60
|
+
# Encoding comment at the beginning throws off line numbers
|
|
61
|
+
# but the string is frozen in ERB 6.0.2+, so we don't want to
|
|
62
|
+
# unnecessarily duplicate the string
|
|
63
|
+
if src.start_with? '#'
|
|
64
|
+
src.sub(/^#.*\n/, '')
|
|
65
|
+
else
|
|
66
|
+
src
|
|
67
|
+
end
|
|
61
68
|
end
|
|
62
69
|
end
|
|
63
70
|
|
|
@@ -171,7 +171,7 @@ class Brakeman::HamlTemplateProcessor < Brakeman::TemplateProcessor
|
|
|
171
171
|
|
|
172
172
|
def escaped_builder_method? exp
|
|
173
173
|
case exp.method
|
|
174
|
-
when :build, :build_aria, :build_boolean, :build_data, :build_id, :escape_html
|
|
174
|
+
when :build, :build_aria, :build_boolean, :build_class, :build_data, :build_id, :escape_html
|
|
175
175
|
true? exp.first_arg
|
|
176
176
|
else
|
|
177
177
|
false
|
|
@@ -91,12 +91,25 @@ class Brakeman::ModelProcessor < Brakeman::BaseProcessor
|
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
def add_enum_method call
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
if call.second_arg.nil?
|
|
95
|
+
# Rails < 7.1 syntax: enum status: { active: 0, archived: 1 }
|
|
96
|
+
arg = call.first_arg
|
|
97
|
+
return unless hash? arg
|
|
98
|
+
return unless symbol? arg[1]
|
|
99
|
+
|
|
100
|
+
enum_name = arg[1].value # first key
|
|
101
|
+
enums = arg[2] # first value
|
|
102
|
+
else
|
|
103
|
+
# Rails >= 7.1 syntax: enum :status, { active: 0, archived: 1 }
|
|
104
|
+
first_arg = call.first_arg
|
|
105
|
+
second_arg = call.second_arg
|
|
106
|
+
return unless symbol? first_arg
|
|
107
|
+
return unless hash? second_arg or array? second_arg
|
|
108
|
+
|
|
109
|
+
enum_name = first_arg.value
|
|
110
|
+
enums = second_arg
|
|
111
|
+
end
|
|
97
112
|
|
|
98
|
-
enum_name = arg[1].value # first key
|
|
99
|
-
enums = arg[2] # first value
|
|
100
113
|
enums_name = pluralize(enum_name.to_s).to_sym
|
|
101
114
|
|
|
102
115
|
call_line = call.line
|
|
@@ -1,38 +1,72 @@
|
|
|
1
1
|
class Brakeman::Report::Sonar < Brakeman::Report::Base
|
|
2
2
|
def generate_report
|
|
3
|
+
warnings = all_warnings
|
|
3
4
|
report_object = {
|
|
4
|
-
|
|
5
|
+
rules: rules_json(warnings),
|
|
6
|
+
issues: warnings.map { |warning| issue_json(warning) }
|
|
5
7
|
}
|
|
6
|
-
|
|
8
|
+
JSON.pretty_generate report_object
|
|
7
9
|
end
|
|
8
|
-
|
|
10
|
+
|
|
9
11
|
private
|
|
10
|
-
|
|
12
|
+
|
|
13
|
+
def rules_json(warnings)
|
|
14
|
+
warnings
|
|
15
|
+
.each_with_object({}) do |w, unique_warnings|
|
|
16
|
+
unique_warnings[[w.warning_code, w.confidence]] ||= w
|
|
17
|
+
end
|
|
18
|
+
.values
|
|
19
|
+
.map do |w|
|
|
20
|
+
{
|
|
21
|
+
id: rule_id(w),
|
|
22
|
+
name: w.warning_type.to_s,
|
|
23
|
+
description: w.link,
|
|
24
|
+
engineId: "Brakeman",
|
|
25
|
+
cleanCodeAttribute: "TRUSTWORTHY",
|
|
26
|
+
type: "VULNERABILITY",
|
|
27
|
+
severity: rule_severity_for(w.confidence),
|
|
28
|
+
impacts: [
|
|
29
|
+
{
|
|
30
|
+
softwareQuality: "SECURITY",
|
|
31
|
+
severity: impact_severity_for(w.confidence)
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
11
38
|
def issue_json(warning)
|
|
12
39
|
{
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
type: "VULNERABILITY",
|
|
16
|
-
severity: severity_level_for(warning.confidence),
|
|
40
|
+
ruleId: rule_id(warning),
|
|
41
|
+
effortMinutes: (4 - warning.confidence) * 15,
|
|
17
42
|
primaryLocation: {
|
|
18
43
|
message: warning.message,
|
|
19
44
|
filePath: warning.file.relative,
|
|
20
45
|
textRange: {
|
|
21
|
-
|
|
22
|
-
|
|
46
|
+
startLine: warning.line || 1,
|
|
47
|
+
endLine: warning.line || 1
|
|
23
48
|
}
|
|
24
|
-
}
|
|
25
|
-
effortMinutes: (4 - warning.confidence) * 15
|
|
49
|
+
}
|
|
26
50
|
}
|
|
27
51
|
end
|
|
28
52
|
|
|
29
|
-
def
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
53
|
+
def rule_id(warning)
|
|
54
|
+
"BRAKEMAN_#{warning.warning_code}_#{rule_severity_for(warning.confidence)}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def rule_severity_for(confidence)
|
|
58
|
+
case confidence
|
|
59
|
+
when 0 then "CRITICAL"
|
|
60
|
+
when 1 then "MAJOR"
|
|
61
|
+
else "MINOR"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def impact_severity_for(confidence)
|
|
66
|
+
case confidence
|
|
67
|
+
when 0 then "HIGH"
|
|
68
|
+
when 1 then "MEDIUM"
|
|
69
|
+
else "LOW"
|
|
36
70
|
end
|
|
37
71
|
end
|
|
38
72
|
end
|
data/lib/brakeman/version.rb
CHANGED
data/lib/brakeman/warning.rb
CHANGED
|
@@ -175,7 +175,7 @@ class Brakeman::Warning
|
|
|
175
175
|
def format_with_user_input strip = true, &block
|
|
176
176
|
if self.user_input
|
|
177
177
|
formatted = Brakeman::OutputProcessor.new.format(code, self.user_input, &block)
|
|
178
|
-
formatted.gsub
|
|
178
|
+
formatted = formatted.gsub(/(\t|\r|\n)+/, " ") if strip
|
|
179
179
|
formatted
|
|
180
180
|
else
|
|
181
181
|
format_code
|
|
@@ -321,4 +321,3 @@ class Brakeman::Warning
|
|
|
321
321
|
formatted
|
|
322
322
|
end
|
|
323
323
|
end
|
|
324
|
-
|
data/lib/brakeman.rb
CHANGED
|
@@ -427,7 +427,7 @@ module Brakeman
|
|
|
427
427
|
release_date = latest.date.to_date
|
|
428
428
|
latest_version = latest.version.to_s
|
|
429
429
|
|
|
430
|
-
if (Date.today -
|
|
430
|
+
if (Date.today - release_date) >= days_old
|
|
431
431
|
if current != latest_version
|
|
432
432
|
return "Brakeman #{current} is not the latest version #{latest_version}"
|
|
433
433
|
else
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: brakeman-lib
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 8.0
|
|
4
|
+
version: 8.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Justin Collins
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-09-30 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: minitest
|
|
@@ -481,6 +482,7 @@ metadata:
|
|
|
481
482
|
mailing_list_uri: https://gitter.im/presidentbeef/brakeman
|
|
482
483
|
source_code_uri: https://github.com/presidentbeef/brakeman
|
|
483
484
|
wiki_uri: https://github.com/presidentbeef/brakeman/wiki
|
|
485
|
+
post_install_message:
|
|
484
486
|
rdoc_options: []
|
|
485
487
|
require_paths:
|
|
486
488
|
- lib
|
|
@@ -495,7 +497,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
495
497
|
- !ruby/object:Gem::Version
|
|
496
498
|
version: '0'
|
|
497
499
|
requirements: []
|
|
498
|
-
rubygems_version:
|
|
500
|
+
rubygems_version: 3.5.11
|
|
501
|
+
signing_key:
|
|
499
502
|
specification_version: 4
|
|
500
503
|
summary: Security vulnerability scanner for Ruby on Rails.
|
|
501
504
|
test_files: []
|