brakeman-min 3.1.5.pre1 → 3.1.5
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 +0 -11
- data/lib/brakeman.rb +4 -4
- data/lib/brakeman/call_index.rb +31 -22
- data/lib/brakeman/checks.rb +73 -59
- data/lib/brakeman/checks/check_basic_auth_timing_attack.rb +21 -0
- data/lib/brakeman/checks/check_cross_site_scripting.rb +2 -2
- data/lib/brakeman/checks/check_render.rb +1 -9
- data/lib/brakeman/checks/check_sql.rb +3 -3
- data/lib/brakeman/processors/alias_processor.rb +1 -1
- data/lib/brakeman/processors/base_processor.rb +0 -8
- data/lib/brakeman/processors/erb_template_processor.rb +1 -1
- data/lib/brakeman/processors/erubis_template_processor.rb +1 -1
- data/lib/brakeman/processors/haml_template_processor.rb +1 -2
- data/lib/brakeman/processors/lib/basic_processor.rb +0 -16
- data/lib/brakeman/processors/lib/find_all_calls.rb +2 -4
- data/lib/brakeman/processors/lib/find_call.rb +1 -1
- data/lib/brakeman/processors/lib/render_path.rb +1 -2
- data/lib/brakeman/report/ignore/config.rb +3 -3
- data/lib/brakeman/report/initializers/faster_csv.rb +7 -0
- data/lib/brakeman/report/initializers/multi_json.rb +29 -0
- data/lib/brakeman/report/report_csv.rb +2 -1
- data/lib/brakeman/report/report_json.rb +4 -1
- data/lib/brakeman/tracker.rb +0 -21
- data/lib/brakeman/util.rb +3 -4
- data/lib/brakeman/version.rb +1 -1
- data/lib/brakeman/warning.rb +2 -2
- data/lib/ruby_parser/bm_sexp.rb +23 -23
- metadata +30 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7cb551015cbef2ba7f9fdc3c08b3ff4263116544
|
|
4
|
+
data.tar.gz: 667a81c5f75bccd9c39bfc4089f1261a601cf80d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 05a344e79c4b52d06d9e76c1875ac9a79aea718646caf1fef4a07e7d40d64609befd09db6733ba7630840f198db02ce4f0f4f4818dd7a9e4dd1f42d9ae9a36dd
|
|
7
|
+
data.tar.gz: a8307a36a59955e671753ebcb7776d22fd1cd5a7cdccb91654e99fc49f030805c603806efe47dbdf9b82bbc67bcf18f63ded4fe177ee546ff34887d363e0dc12
|
data/CHANGES
CHANGED
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
# 3.2.0.pre1
|
|
2
|
-
|
|
3
|
-
* Support calls using `&.` operator
|
|
4
|
-
* Update ruby_parser dependency to 3.8.1
|
|
5
|
-
* Remove `fastercsv` dependency
|
|
6
|
-
* Fix finding calls with `targets: nil`
|
|
7
|
-
* Remove `multi-json` dependecy
|
|
8
|
-
* Handle CoffeeScript in HAML
|
|
9
|
-
* Avoid render warnings about params[:action]/params[:controller]
|
|
10
|
-
* Index calls in class bodies but outside methods
|
|
11
|
-
|
|
12
1
|
# 3.1.5
|
|
13
2
|
|
|
14
3
|
* Fix CodeClimate construction of --only-files (Will Fleming)
|
data/lib/brakeman.rb
CHANGED
|
@@ -407,20 +407,20 @@ module Brakeman
|
|
|
407
407
|
|
|
408
408
|
# Compare JSON ouptut from a previous scan and return the diff of the two scans
|
|
409
409
|
def self.compare options
|
|
410
|
-
require '
|
|
410
|
+
require 'multi_json'
|
|
411
411
|
require 'brakeman/differ'
|
|
412
412
|
raise ArgumentError.new("Comparison file doesn't exist") unless File.exist? options[:previous_results_json]
|
|
413
413
|
|
|
414
414
|
begin
|
|
415
|
-
previous_results =
|
|
416
|
-
rescue
|
|
415
|
+
previous_results = MultiJson.load(File.read(options[:previous_results_json]), :symbolize_keys => true)[:warnings]
|
|
416
|
+
rescue MultiJson::DecodeError
|
|
417
417
|
self.notify "Error parsing comparison file: #{options[:previous_results_json]}"
|
|
418
418
|
exit!
|
|
419
419
|
end
|
|
420
420
|
|
|
421
421
|
tracker = run(options)
|
|
422
422
|
|
|
423
|
-
new_results =
|
|
423
|
+
new_results = MultiJson.load(tracker.report.to_json, :symbolize_keys => true)[:warnings]
|
|
424
424
|
|
|
425
425
|
Brakeman::Differ.new(new_results, previous_results).diff
|
|
426
426
|
end
|
data/lib/brakeman/call_index.rb
CHANGED
|
@@ -5,8 +5,8 @@ class Brakeman::CallIndex
|
|
|
5
5
|
|
|
6
6
|
#Initialize index with calls from FindAllCalls
|
|
7
7
|
def initialize calls
|
|
8
|
-
@calls_by_method = Hash.new
|
|
9
|
-
@calls_by_target = Hash.new
|
|
8
|
+
@calls_by_method = Hash.new
|
|
9
|
+
@calls_by_target = Hash.new
|
|
10
10
|
|
|
11
11
|
index_calls calls
|
|
12
12
|
end
|
|
@@ -45,7 +45,7 @@ class Brakeman::CallIndex
|
|
|
45
45
|
|
|
46
46
|
#Find calls with no explicit target
|
|
47
47
|
#with either :target => nil or :target => false
|
|
48
|
-
elsif
|
|
48
|
+
elsif options.key? :target and not target and method
|
|
49
49
|
calls = calls_by_method method
|
|
50
50
|
calls = filter_by_target calls, nil
|
|
51
51
|
|
|
@@ -66,35 +66,44 @@ class Brakeman::CallIndex
|
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def remove_template_indexes template_name = nil
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
@calls_by_method.each do |name, calls|
|
|
70
|
+
calls.delete_if do |call|
|
|
71
|
+
from_template call, template_name
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
@calls_by_target.each do |name, calls|
|
|
76
|
+
calls.delete_if do |call|
|
|
77
|
+
from_template call, template_name
|
|
74
78
|
end
|
|
75
79
|
end
|
|
76
80
|
end
|
|
77
81
|
|
|
78
82
|
def remove_indexes_by_class classes
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
@calls_by_method.each do |name, calls|
|
|
84
|
+
calls.delete_if do |call|
|
|
85
|
+
call[:location][:type] == :class and classes.include? call[:location][:class]
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
@calls_by_target.each do |name, calls|
|
|
90
|
+
calls.delete_if do |call|
|
|
91
|
+
call[:location][:type] == :class and classes.include? call[:location][:class]
|
|
84
92
|
end
|
|
85
93
|
end
|
|
86
94
|
end
|
|
87
95
|
|
|
88
96
|
def index_calls calls
|
|
89
97
|
calls.each do |call|
|
|
98
|
+
@calls_by_method[call[:method]] ||= []
|
|
90
99
|
@calls_by_method[call[:method]] << call
|
|
91
100
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
@calls_by_target[target.node_type] << call
|
|
101
|
+
if not call[:target].is_a? Sexp
|
|
102
|
+
@calls_by_target[call[:target]] ||= []
|
|
103
|
+
@calls_by_target[call[:target]] << call
|
|
104
|
+
elsif call[:target].node_type == :params or call[:target].node_type == :session
|
|
105
|
+
@calls_by_target[call[:target].node_type] ||= []
|
|
106
|
+
@calls_by_target[call[:target].node_type] << call
|
|
98
107
|
end
|
|
99
108
|
end
|
|
100
109
|
end
|
|
@@ -106,7 +115,7 @@ class Brakeman::CallIndex
|
|
|
106
115
|
method = options[:method] || options[:methods]
|
|
107
116
|
|
|
108
117
|
calls = calls_by_method method
|
|
109
|
-
|
|
118
|
+
|
|
110
119
|
return [] if calls.nil?
|
|
111
120
|
|
|
112
121
|
calls = filter_by_chain calls, target
|
|
@@ -136,7 +145,7 @@ class Brakeman::CallIndex
|
|
|
136
145
|
elsif method.is_a? Regexp
|
|
137
146
|
calls_by_methods_regex method
|
|
138
147
|
else
|
|
139
|
-
@calls_by_method[method.to_sym]
|
|
148
|
+
@calls_by_method[method.to_sym] || []
|
|
140
149
|
end
|
|
141
150
|
end
|
|
142
151
|
|
|
@@ -160,7 +169,7 @@ class Brakeman::CallIndex
|
|
|
160
169
|
end
|
|
161
170
|
|
|
162
171
|
def calls_with_no_target
|
|
163
|
-
@calls_by_target[nil]
|
|
172
|
+
@calls_by_target[nil] || []
|
|
164
173
|
end
|
|
165
174
|
|
|
166
175
|
def filter calls, key, value
|
data/lib/brakeman/checks.rb
CHANGED
|
@@ -93,49 +93,91 @@ class Brakeman::Checks
|
|
|
93
93
|
#Run all the checks on the given Tracker.
|
|
94
94
|
#Returns a new instance of Checks with the results.
|
|
95
95
|
def self.run_checks(app_tree, tracker)
|
|
96
|
-
|
|
96
|
+
if tracker.options[:parallel_checks]
|
|
97
|
+
self.run_checks_parallel(app_tree, tracker)
|
|
98
|
+
else
|
|
99
|
+
self.run_checks_sequential(app_tree, tracker)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
#Run checks sequentially
|
|
104
|
+
def self.run_checks_sequential(app_tree, tracker)
|
|
97
105
|
check_runner = self.new :min_confidence => tracker.options[:min_confidence]
|
|
98
|
-
|
|
106
|
+
|
|
107
|
+
self.checks_to_run(tracker).each do |c|
|
|
108
|
+
check_name = get_check_name c
|
|
109
|
+
|
|
110
|
+
#Run or don't run check based on options
|
|
111
|
+
unless tracker.options[:skip_checks].include? check_name or
|
|
112
|
+
(tracker.options[:run_checks] and not tracker.options[:run_checks].include? check_name)
|
|
113
|
+
|
|
114
|
+
Brakeman.notify " - #{check_name}"
|
|
115
|
+
|
|
116
|
+
check = c.new(app_tree, tracker)
|
|
117
|
+
|
|
118
|
+
begin
|
|
119
|
+
check.run_check
|
|
120
|
+
rescue => e
|
|
121
|
+
tracker.error e
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
check.warnings.each do |w|
|
|
125
|
+
check_runner.add_warning w
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
#Maintain list of which checks were run
|
|
129
|
+
#mainly for reporting purposes
|
|
130
|
+
check_runner.checks_run << check_name[5..-1]
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
check_runner
|
|
99
135
|
end
|
|
100
136
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
parallel = tracker.options[:parallel_checks]
|
|
137
|
+
#Run checks in parallel threads
|
|
138
|
+
def self.run_checks_parallel(app_tree, tracker)
|
|
139
|
+
threads = []
|
|
105
140
|
error_mutex = Mutex.new
|
|
106
141
|
|
|
107
|
-
|
|
142
|
+
check_runner = self.new :min_confidence => tracker.options[:min_confidence]
|
|
143
|
+
|
|
144
|
+
self.checks_to_run(tracker).each do |c|
|
|
108
145
|
check_name = get_check_name c
|
|
109
|
-
Brakeman.notify " - #{check_name}"
|
|
110
146
|
|
|
111
|
-
|
|
147
|
+
#Run or don't run check based on options
|
|
148
|
+
unless tracker.options[:skip_checks].include? check_name or
|
|
149
|
+
(tracker.options[:run_checks] and not tracker.options[:run_checks].include? check_name)
|
|
150
|
+
|
|
151
|
+
Brakeman.notify " - #{check_name}"
|
|
152
|
+
|
|
112
153
|
threads << Thread.new do
|
|
113
|
-
|
|
154
|
+
check = c.new(app_tree, tracker)
|
|
155
|
+
|
|
156
|
+
begin
|
|
157
|
+
check.run_check
|
|
158
|
+
rescue => e
|
|
159
|
+
error_mutex.synchronize do
|
|
160
|
+
tracker.error e
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
check.warnings
|
|
114
165
|
end
|
|
115
|
-
else
|
|
116
|
-
results << self.run_a_check(c, error_mutex, app_tree, tracker)
|
|
117
|
-
end
|
|
118
166
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
167
|
+
#Maintain list of which checks were run
|
|
168
|
+
#mainly for reporting purposes
|
|
169
|
+
check_runner.checks_run << check_name[5..-1]
|
|
170
|
+
end
|
|
122
171
|
end
|
|
123
172
|
|
|
124
173
|
threads.each { |t| t.join }
|
|
125
174
|
|
|
126
175
|
Brakeman.notify "Checks finished, collecting results..."
|
|
127
176
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
else
|
|
135
|
-
results.each do |warnings|
|
|
136
|
-
warnings.each do |warning|
|
|
137
|
-
check_runner.add_warning warning
|
|
138
|
-
end
|
|
177
|
+
#Collect results
|
|
178
|
+
threads.each do |thread|
|
|
179
|
+
thread.value.each do |warning|
|
|
180
|
+
check_runner.add_warning warning
|
|
139
181
|
end
|
|
140
182
|
end
|
|
141
183
|
|
|
@@ -149,39 +191,11 @@ class Brakeman::Checks
|
|
|
149
191
|
end
|
|
150
192
|
|
|
151
193
|
def self.checks_to_run tracker
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
self.filter_checks to_run, tracker
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
def self.filter_checks checks, tracker
|
|
162
|
-
skipped = tracker.options[:skip_checks]
|
|
163
|
-
explicit = tracker.options[:run_checks]
|
|
164
|
-
|
|
165
|
-
checks.reject do |c|
|
|
166
|
-
check_name = self.get_check_name(c)
|
|
167
|
-
|
|
168
|
-
skipped.include? check_name or
|
|
169
|
-
(explicit and not explicit.include? check_name)
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
def self.run_a_check klass, mutex, app_tree, tracker
|
|
174
|
-
check = klass.new(app_tree, tracker)
|
|
175
|
-
|
|
176
|
-
begin
|
|
177
|
-
check.run_check
|
|
178
|
-
rescue => e
|
|
179
|
-
mutex.synchronize do
|
|
180
|
-
tracker.error e
|
|
181
|
-
end
|
|
194
|
+
if tracker.options[:run_all_checks] or tracker.options[:run_checks]
|
|
195
|
+
@checks + @optional_checks
|
|
196
|
+
else
|
|
197
|
+
@checks
|
|
182
198
|
end
|
|
183
|
-
|
|
184
|
-
check.warnings
|
|
185
199
|
end
|
|
186
200
|
end
|
|
187
201
|
|
|
@@ -17,10 +17,31 @@ class Brakeman::CheckBasicAuthTimingAttack < Brakeman::BaseCheck
|
|
|
17
17
|
return
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
check_basic_auth_filter
|
|
20
21
|
check_basic_auth_call
|
|
21
22
|
end
|
|
22
23
|
|
|
24
|
+
def check_basic_auth_filter
|
|
25
|
+
controllers = tracker.controllers.select do |name, c|
|
|
26
|
+
c.options[:http_basic_authenticate_with]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
Hash[controllers].each do |name, controller|
|
|
30
|
+
controller.options[:http_basic_authenticate_with].each do |call|
|
|
31
|
+
warn :controller => name,
|
|
32
|
+
:warning_type => "Timing Attack",
|
|
33
|
+
:warning_code => :CVE_2015_7576,
|
|
34
|
+
:message => "Basic authentication in Rails #{rails_version} is vulnerable to timing attacks. Upgrade to #@upgrade",
|
|
35
|
+
:code => call,
|
|
36
|
+
:confidence => CONFIDENCE[:high],
|
|
37
|
+
:file => controller.file,
|
|
38
|
+
:link => "https://groups.google.com/d/msg/rubyonrails-security/ANv0HDHEC3k/mt7wNGxbFQAJ"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
23
43
|
def check_basic_auth_call
|
|
44
|
+
# This is relatively unusual, but found in the wild
|
|
24
45
|
tracker.find_call(target: nil, method: :http_basic_authenticate_with).each do |result|
|
|
25
46
|
warn :result => result,
|
|
26
47
|
:warning_type => "Timing Attack",
|
|
@@ -99,7 +99,7 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
|
|
|
99
99
|
link_path = "cross_site_scripting"
|
|
100
100
|
warning_code = :cross_site_scripting
|
|
101
101
|
|
|
102
|
-
if node_type?(out, :call, :
|
|
102
|
+
if node_type?(out, :call, :attrasgn) && out.method == :to_json
|
|
103
103
|
message += " in JSON hash"
|
|
104
104
|
link_path += "_to_json"
|
|
105
105
|
warning_code = :xss_to_json
|
|
@@ -334,7 +334,7 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
|
|
|
334
334
|
end
|
|
335
335
|
|
|
336
336
|
def html_safe_call? exp
|
|
337
|
-
|
|
337
|
+
exp.value.node_type == :call and exp.value.method == :html_safe
|
|
338
338
|
end
|
|
339
339
|
|
|
340
340
|
def ignore_call? target, method
|
|
@@ -35,6 +35,7 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
|
|
|
35
35
|
if sexp? view and not duplicate? result
|
|
36
36
|
add_result result
|
|
37
37
|
|
|
38
|
+
|
|
38
39
|
if input = has_immediate_user_input?(view)
|
|
39
40
|
if string_interp? view
|
|
40
41
|
confidence = CONFIDENCE[:med]
|
|
@@ -48,7 +49,6 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
|
|
|
48
49
|
end
|
|
49
50
|
|
|
50
51
|
return if input.type == :model #skip models
|
|
51
|
-
return if safe_param? input.match
|
|
52
52
|
|
|
53
53
|
message = "Render path contains #{friendly_type_of input}"
|
|
54
54
|
|
|
@@ -71,7 +71,6 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
|
|
|
71
71
|
if sexp? view and not duplicate? result
|
|
72
72
|
if params? view
|
|
73
73
|
add_result result
|
|
74
|
-
return if safe_param? view
|
|
75
74
|
|
|
76
75
|
warn :result => result,
|
|
77
76
|
:warning_type => "Remote Code Execution",
|
|
@@ -82,11 +81,4 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
|
|
|
82
81
|
end
|
|
83
82
|
end
|
|
84
83
|
end
|
|
85
|
-
|
|
86
|
-
def safe_param? exp
|
|
87
|
-
if params? exp and call? exp and exp.method == :[]
|
|
88
|
-
arg = exp.first_arg
|
|
89
|
-
symbol? arg and [:controller, :action].include? arg.value
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
84
|
end
|
|
@@ -64,9 +64,9 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
|
|
|
64
64
|
second_arg = args[2]
|
|
65
65
|
next unless sexp? second_arg
|
|
66
66
|
|
|
67
|
-
if second_arg.node_type == :iter and node_type? second_arg.block, :block, :call
|
|
67
|
+
if second_arg.node_type == :iter and node_type? second_arg.block, :block, :call
|
|
68
68
|
process_scope_with_block(name, args)
|
|
69
|
-
elsif call
|
|
69
|
+
elsif second_arg.node_type == :call
|
|
70
70
|
call = second_arg
|
|
71
71
|
scope_calls << scope_call_hash(call, name, call.method)
|
|
72
72
|
else
|
|
@@ -107,7 +107,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
|
|
|
107
107
|
find_calls = Brakeman::FindAllCalls.new(tracker)
|
|
108
108
|
find_calls.process_source(block, :class => model_name, :method => scope_name)
|
|
109
109
|
find_calls.calls.each { |call| process_result(call) if @sql_targets.include?(call[:method]) }
|
|
110
|
-
elsif call
|
|
110
|
+
elsif block.node_type == :call
|
|
111
111
|
while call? block
|
|
112
112
|
process_result :target => block.target, :method => block.method, :call => block,
|
|
113
113
|
:location => { :type => :class, :class => model_name, :method => scope_name }
|
|
@@ -296,7 +296,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
|
296
296
|
|
|
297
297
|
# Handles x = y = z = 1
|
|
298
298
|
def get_rhs exp
|
|
299
|
-
if node_type? exp, :lasgn, :iasgn, :gasgn, :attrasgn, :
|
|
299
|
+
if node_type? exp, :lasgn, :iasgn, :gasgn, :attrasgn, :cvdecl, :cdecl
|
|
300
300
|
get_rhs(exp.rhs)
|
|
301
301
|
else
|
|
302
302
|
exp
|
|
@@ -68,14 +68,6 @@ class Brakeman::BaseProcessor < Brakeman::SexpProcessor
|
|
|
68
68
|
call
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
-
def process_safe_call exp
|
|
72
|
-
if self.respond_to? :process_call
|
|
73
|
-
process_call exp
|
|
74
|
-
else
|
|
75
|
-
process_default exp
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
71
|
#String with interpolation.
|
|
80
72
|
def process_dstr exp
|
|
81
73
|
exp = exp.dup
|
|
@@ -25,7 +25,7 @@ class Brakeman::ErbTemplateProcessor < Brakeman::TemplateProcessor
|
|
|
25
25
|
|
|
26
26
|
arg = exp.first_arg
|
|
27
27
|
|
|
28
|
-
if call
|
|
28
|
+
if arg.node_type == :call and arg.method == :to_s #erb always calls to_s on output
|
|
29
29
|
arg = arg.target
|
|
30
30
|
end
|
|
31
31
|
|
|
@@ -21,7 +21,7 @@ class Brakeman::ErubisTemplateProcessor < Brakeman::TemplateProcessor
|
|
|
21
21
|
arg = exp.first_arg
|
|
22
22
|
|
|
23
23
|
#We want the actual content
|
|
24
|
-
if call
|
|
24
|
+
if arg.node_type == :call and (arg.method == :to_s or arg.method == :html_safe!)
|
|
25
25
|
arg = arg.target
|
|
26
26
|
end
|
|
27
27
|
|
|
@@ -5,7 +5,6 @@ class Brakeman::HamlTemplateProcessor < Brakeman::TemplateProcessor
|
|
|
5
5
|
HAML_FORMAT_METHOD = /format_script_(true|false)_(true|false)_(true|false)_(true|false)_(true|false)_(true|false)_(true|false)/
|
|
6
6
|
HAML_HELPERS = s(:colon2, s(:const, :Haml), :Helpers)
|
|
7
7
|
JAVASCRIPT_FILTER = s(:colon2, s(:colon2, s(:const, :Haml), :Filters), :Javascript)
|
|
8
|
-
COFFEE_FILTER = s(:colon2, s(:colon2, s(:const, :Haml), :Filters), :Coffee)
|
|
9
8
|
|
|
10
9
|
#Processes call, looking for template output
|
|
11
10
|
def process_call exp
|
|
@@ -91,7 +90,7 @@ class Brakeman::HamlTemplateProcessor < Brakeman::TemplateProcessor
|
|
|
91
90
|
elsif target == nil and method == :find_and_preserve
|
|
92
91
|
process exp.first_arg
|
|
93
92
|
elsif method == :render_with_options
|
|
94
|
-
if target == JAVASCRIPT_FILTER
|
|
93
|
+
if target == JAVASCRIPT_FILTER
|
|
95
94
|
@javascript = true
|
|
96
95
|
end
|
|
97
96
|
|
|
@@ -14,20 +14,4 @@ class Brakeman::BasicProcessor < Brakeman::SexpProcessor
|
|
|
14
14
|
def process_default exp
|
|
15
15
|
process_all exp
|
|
16
16
|
end
|
|
17
|
-
|
|
18
|
-
def process_safe_call exp
|
|
19
|
-
if self.respond_to? :process_call
|
|
20
|
-
process_call exp
|
|
21
|
-
else
|
|
22
|
-
process_default exp
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def process_safe_attrasgn exp
|
|
27
|
-
if self.respond_to? :process_attrasgn
|
|
28
|
-
process_attrasgn exp
|
|
29
|
-
else
|
|
30
|
-
process_default exp
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
17
|
end
|
|
@@ -24,13 +24,11 @@ class Brakeman::FindAllCalls < Brakeman::BasicProcessor
|
|
|
24
24
|
|
|
25
25
|
#Process body of method
|
|
26
26
|
def process_defn exp
|
|
27
|
-
return exp unless @current_method
|
|
28
27
|
process_all exp.body
|
|
29
28
|
end
|
|
30
29
|
|
|
31
30
|
#Process body of method
|
|
32
31
|
def process_defs exp
|
|
33
|
-
return exp unless @current_method
|
|
34
32
|
process_all exp.body
|
|
35
33
|
end
|
|
36
34
|
|
|
@@ -141,7 +139,7 @@ class Brakeman::FindAllCalls < Brakeman::BasicProcessor
|
|
|
141
139
|
@current_class || @current_module || nil
|
|
142
140
|
when :params, :session, :cookies
|
|
143
141
|
exp.node_type
|
|
144
|
-
when :call
|
|
142
|
+
when :call
|
|
145
143
|
if include_calls
|
|
146
144
|
if exp.target.nil?
|
|
147
145
|
exp.method
|
|
@@ -167,7 +165,7 @@ class Brakeman::FindAllCalls < Brakeman::BasicProcessor
|
|
|
167
165
|
#Returns method chain as an array
|
|
168
166
|
#For example, User.human.alive.all would return [:User, :human, :alive, :all]
|
|
169
167
|
def get_chain call
|
|
170
|
-
if node_type? call, :call, :attrasgn
|
|
168
|
+
if node_type? call, :call, :attrasgn
|
|
171
169
|
get_chain(call.target) + [call.method]
|
|
172
170
|
elsif call.nil?
|
|
173
171
|
[]
|
|
@@ -102,7 +102,7 @@ class Brakeman::FindCall < Brakeman::BasicProcessor
|
|
|
102
102
|
# User.find(:first, :conditions => "user = '#{params['user']}').name
|
|
103
103
|
#
|
|
104
104
|
#A search for User.find will not match this unless @in_depth is true.
|
|
105
|
-
if @in_depth and
|
|
105
|
+
if @in_depth and node_type? exp.target, :call
|
|
106
106
|
process exp.target
|
|
107
107
|
end
|
|
108
108
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
require 'set'
|
|
2
|
-
require '
|
|
2
|
+
require 'multi_json'
|
|
3
3
|
|
|
4
4
|
module Brakeman
|
|
5
5
|
class IgnoreConfig
|
|
@@ -75,7 +75,7 @@ module Brakeman
|
|
|
75
75
|
# Read configuration to file
|
|
76
76
|
def read_from_file file = @file
|
|
77
77
|
if File.exist? file
|
|
78
|
-
@already_ignored =
|
|
78
|
+
@already_ignored = MultiJson.load(File.read(file), :symbolize_keys => true)[:ignored_warnings]
|
|
79
79
|
else
|
|
80
80
|
Brakeman.notify "[Notice] Could not find ignore configuration in #{file}"
|
|
81
81
|
@already_ignored = []
|
|
@@ -107,7 +107,7 @@ module Brakeman
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
File.open file, "w" do |f|
|
|
110
|
-
f.puts
|
|
110
|
+
f.puts MultiJson.dump(output, :pretty => true)
|
|
111
111
|
end
|
|
112
112
|
end
|
|
113
113
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#MultiJson interface changed in 1.3.0, but need
|
|
2
|
+
#to support older MultiJson for Rails 3.1.
|
|
3
|
+
mj_engine = nil
|
|
4
|
+
|
|
5
|
+
if MultiJson.respond_to? :default_adapter
|
|
6
|
+
mj_engine = MultiJson.default_adapter
|
|
7
|
+
else
|
|
8
|
+
mj_engine = MultiJson.default_engine
|
|
9
|
+
|
|
10
|
+
module MultiJson
|
|
11
|
+
def self.dump *args
|
|
12
|
+
encode *args
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.load *args
|
|
16
|
+
decode *args
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
#This is so OkJson will work with symbol values
|
|
22
|
+
if mj_engine == :ok_json
|
|
23
|
+
class Symbol
|
|
24
|
+
def to_json
|
|
25
|
+
self.to_s.inspect
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
Brakeman.load_brakeman_dependency 'multi_json'
|
|
2
|
+
require 'brakeman/report/initializers/multi_json'
|
|
3
|
+
|
|
1
4
|
class Brakeman::Report::JSON < Brakeman::Report::Base
|
|
2
5
|
def generate_report
|
|
3
6
|
errors = tracker.errors.map{|e| { :error => e[:error], :location => e[:backtrace][0] }}
|
|
@@ -29,7 +32,7 @@ class Brakeman::Report::JSON < Brakeman::Report::Base
|
|
|
29
32
|
:errors => errors
|
|
30
33
|
}
|
|
31
34
|
|
|
32
|
-
|
|
35
|
+
MultiJson.dump(report_info, :pretty => true)
|
|
33
36
|
end
|
|
34
37
|
|
|
35
38
|
def convert_to_hashes warnings
|
data/lib/brakeman/tracker.rb
CHANGED
|
@@ -115,23 +115,6 @@ class Brakeman::Tracker
|
|
|
115
115
|
end
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
def each_class
|
|
120
|
-
classes = [self.controllers, self.models]
|
|
121
|
-
|
|
122
|
-
if @options[:index_libs]
|
|
123
|
-
classes << self.libs
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
classes.each do |set|
|
|
127
|
-
set.each do |set_name, collection|
|
|
128
|
-
collection.src.each do |file, src|
|
|
129
|
-
yield src, set_name, file
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
|
|
135
118
|
#Find a method call.
|
|
136
119
|
#
|
|
137
120
|
#Options:
|
|
@@ -195,10 +178,6 @@ class Brakeman::Tracker
|
|
|
195
178
|
finder.process_source definition, :class => set_name, :method => method_name, :file => file
|
|
196
179
|
end
|
|
197
180
|
|
|
198
|
-
self.each_class do |definition, set_name, file|
|
|
199
|
-
finder.process_source definition, :class => set_name, :file => file
|
|
200
|
-
end
|
|
201
|
-
|
|
202
181
|
self.each_template do |name, template|
|
|
203
182
|
finder.process_source template.src, :template => template, :file => template.file
|
|
204
183
|
end
|
data/lib/brakeman/util.rb
CHANGED
|
@@ -167,8 +167,7 @@ module Brakeman::Util
|
|
|
167
167
|
|
|
168
168
|
#Check if _exp_ represents a method call: s(:call, ...)
|
|
169
169
|
def call? exp
|
|
170
|
-
exp.is_a? Sexp and
|
|
171
|
-
(exp.node_type == :call or exp.node_type == :safe_call)
|
|
170
|
+
exp.is_a? Sexp and exp.node_type == :call
|
|
172
171
|
end
|
|
173
172
|
|
|
174
173
|
#Check if _exp_ represents a Regexp: s(:lit, /.../)
|
|
@@ -215,7 +214,7 @@ module Brakeman::Util
|
|
|
215
214
|
if exp.is_a? Sexp
|
|
216
215
|
return true if exp.node_type == :params or ALL_PARAMETERS.include? exp
|
|
217
216
|
|
|
218
|
-
if call
|
|
217
|
+
if exp.node_type == :call
|
|
219
218
|
if params? exp[1]
|
|
220
219
|
return true
|
|
221
220
|
elsif exp[2] == :[]
|
|
@@ -231,7 +230,7 @@ module Brakeman::Util
|
|
|
231
230
|
if exp.is_a? Sexp
|
|
232
231
|
return true if exp.node_type == :cookies or exp == COOKIES
|
|
233
232
|
|
|
234
|
-
if call
|
|
233
|
+
if exp.node_type == :call
|
|
235
234
|
if cookies? exp[1]
|
|
236
235
|
return true
|
|
237
236
|
elsif exp[2] == :[]
|
data/lib/brakeman/version.rb
CHANGED
data/lib/brakeman/warning.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'multi_json'
|
|
2
2
|
require 'digest/sha2'
|
|
3
3
|
require 'brakeman/warning_codes'
|
|
4
4
|
|
|
@@ -241,7 +241,7 @@ class Brakeman::Warning
|
|
|
241
241
|
end
|
|
242
242
|
|
|
243
243
|
def to_json
|
|
244
|
-
|
|
244
|
+
MultiJson.dump self.to_hash
|
|
245
245
|
end
|
|
246
246
|
|
|
247
247
|
private
|
data/lib/ruby_parser/bm_sexp.rb
CHANGED
|
@@ -141,13 +141,13 @@ class Sexp
|
|
|
141
141
|
#s(:call, s(:call, nil, :x, s(:arglist)), :y, s(:arglist, s(:lit, 1)))
|
|
142
142
|
# ^-----------target-----------^
|
|
143
143
|
def target
|
|
144
|
-
expect :call, :attrasgn
|
|
144
|
+
expect :call, :attrasgn
|
|
145
145
|
self[1]
|
|
146
146
|
end
|
|
147
147
|
|
|
148
148
|
#Sets the target of a method call:
|
|
149
149
|
def target= exp
|
|
150
|
-
expect :call, :attrasgn
|
|
150
|
+
expect :call, :attrasgn
|
|
151
151
|
@my_hash_value = nil
|
|
152
152
|
self[1] = exp
|
|
153
153
|
end
|
|
@@ -157,10 +157,10 @@ class Sexp
|
|
|
157
157
|
#s(:call, s(:call, nil, :x, s(:arglist)), :y, s(:arglist, s(:lit, 1)))
|
|
158
158
|
# ^- method
|
|
159
159
|
def method
|
|
160
|
-
expect :call, :attrasgn, :
|
|
160
|
+
expect :call, :attrasgn, :super, :zsuper, :result
|
|
161
161
|
|
|
162
162
|
case self.node_type
|
|
163
|
-
when :call, :attrasgn
|
|
163
|
+
when :call, :attrasgn
|
|
164
164
|
self[2]
|
|
165
165
|
when :super, :zsuper
|
|
166
166
|
:super
|
|
@@ -170,14 +170,14 @@ class Sexp
|
|
|
170
170
|
end
|
|
171
171
|
|
|
172
172
|
def method= name
|
|
173
|
-
expect :call
|
|
173
|
+
expect :call
|
|
174
174
|
|
|
175
175
|
self[2] = name
|
|
176
176
|
end
|
|
177
177
|
|
|
178
178
|
#Sets the arglist in a method call.
|
|
179
179
|
def arglist= exp
|
|
180
|
-
expect :call, :attrasgn
|
|
180
|
+
expect :call, :attrasgn
|
|
181
181
|
@my_hash_value = nil
|
|
182
182
|
start_index = 3
|
|
183
183
|
|
|
@@ -201,10 +201,10 @@ class Sexp
|
|
|
201
201
|
# s(:call, s(:call, nil, :x, s(:arglist)), :y, s(:arglist, s(:lit, 1), s(:lit, 2)))
|
|
202
202
|
# ^------------ arglist ------------^
|
|
203
203
|
def arglist
|
|
204
|
-
expect :call, :attrasgn, :
|
|
204
|
+
expect :call, :attrasgn, :super, :zsuper
|
|
205
205
|
|
|
206
206
|
case self.node_type
|
|
207
|
-
when :call, :attrasgn
|
|
207
|
+
when :call, :attrasgn
|
|
208
208
|
self[3..-1].unshift :arglist
|
|
209
209
|
when :super, :zsuper
|
|
210
210
|
if self[1]
|
|
@@ -220,10 +220,10 @@ class Sexp
|
|
|
220
220
|
# s(:call, s(:call, nil, :x, s(:arglist)), :y, s(:arglist, s(:lit, 1), s(:lit, 2)))
|
|
221
221
|
# ^--------args--------^
|
|
222
222
|
def args
|
|
223
|
-
expect :call, :attrasgn, :
|
|
223
|
+
expect :call, :attrasgn, :super, :zsuper
|
|
224
224
|
|
|
225
225
|
case self.node_type
|
|
226
|
-
when :call, :attrasgn
|
|
226
|
+
when :call, :attrasgn
|
|
227
227
|
if self[3]
|
|
228
228
|
self[3..-1]
|
|
229
229
|
else
|
|
@@ -239,11 +239,11 @@ class Sexp
|
|
|
239
239
|
end
|
|
240
240
|
|
|
241
241
|
def each_arg replace = false
|
|
242
|
-
expect :call, :attrasgn, :
|
|
242
|
+
expect :call, :attrasgn, :super, :zsuper
|
|
243
243
|
range = nil
|
|
244
244
|
|
|
245
245
|
case self.node_type
|
|
246
|
-
when :call, :attrasgn
|
|
246
|
+
when :call, :attrasgn
|
|
247
247
|
if self[3]
|
|
248
248
|
range = (3...self.length)
|
|
249
249
|
end
|
|
@@ -270,43 +270,43 @@ class Sexp
|
|
|
270
270
|
|
|
271
271
|
#Returns first argument of a method call.
|
|
272
272
|
def first_arg
|
|
273
|
-
expect :call, :attrasgn
|
|
273
|
+
expect :call, :attrasgn
|
|
274
274
|
self[3]
|
|
275
275
|
end
|
|
276
276
|
|
|
277
277
|
#Sets first argument of a method call.
|
|
278
278
|
def first_arg= exp
|
|
279
|
-
expect :call, :attrasgn
|
|
279
|
+
expect :call, :attrasgn
|
|
280
280
|
@my_hash_value = nil
|
|
281
281
|
self[3] = exp
|
|
282
282
|
end
|
|
283
283
|
|
|
284
284
|
#Returns second argument of a method call.
|
|
285
285
|
def second_arg
|
|
286
|
-
expect :call, :attrasgn
|
|
286
|
+
expect :call, :attrasgn
|
|
287
287
|
self[4]
|
|
288
288
|
end
|
|
289
289
|
|
|
290
290
|
#Sets second argument of a method call.
|
|
291
291
|
def second_arg= exp
|
|
292
|
-
expect :call, :attrasgn
|
|
292
|
+
expect :call, :attrasgn
|
|
293
293
|
@my_hash_value = nil
|
|
294
294
|
self[4] = exp
|
|
295
295
|
end
|
|
296
296
|
|
|
297
297
|
def third_arg
|
|
298
|
-
expect :call, :attrasgn
|
|
298
|
+
expect :call, :attrasgn
|
|
299
299
|
self[5]
|
|
300
300
|
end
|
|
301
301
|
|
|
302
302
|
def third_arg= exp
|
|
303
|
-
expect :call, :attrasgn
|
|
303
|
+
expect :call, :attrasgn
|
|
304
304
|
@my_hash_value = nil
|
|
305
305
|
self[5] = exp
|
|
306
306
|
end
|
|
307
307
|
|
|
308
308
|
def last_arg
|
|
309
|
-
expect :call, :attrasgn
|
|
309
|
+
expect :call, :attrasgn
|
|
310
310
|
|
|
311
311
|
if self[3]
|
|
312
312
|
self[-1]
|
|
@@ -427,9 +427,9 @@ class Sexp
|
|
|
427
427
|
# s(:lasgn, :x, s(:lit, 1))
|
|
428
428
|
# ^--rhs---^
|
|
429
429
|
def rhs
|
|
430
|
-
expect :attrasgn,
|
|
430
|
+
expect :attrasgn, *ASSIGNMENT_BOOL
|
|
431
431
|
|
|
432
|
-
if self.node_type == :attrasgn
|
|
432
|
+
if self.node_type == :attrasgn
|
|
433
433
|
self[3]
|
|
434
434
|
else
|
|
435
435
|
self[2]
|
|
@@ -438,10 +438,10 @@ class Sexp
|
|
|
438
438
|
|
|
439
439
|
#Sets the right hand side of assignment or boolean.
|
|
440
440
|
def rhs= exp
|
|
441
|
-
expect :attrasgn,
|
|
441
|
+
expect :attrasgn, *ASSIGNMENT_BOOL
|
|
442
442
|
@my_hash_value = nil
|
|
443
443
|
|
|
444
|
-
if self.node_type == :attrasgn
|
|
444
|
+
if self.node_type == :attrasgn
|
|
445
445
|
self[3] = exp
|
|
446
446
|
else
|
|
447
447
|
self[2] = exp
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: brakeman-min
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.1.5
|
|
4
|
+
version: 3.1.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Justin Collins
|
|
@@ -9,7 +9,7 @@ autorequire:
|
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain:
|
|
11
11
|
- brakeman-public_cert.pem
|
|
12
|
-
date: 2016-
|
|
12
|
+
date: 2016-01-28 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: test-unit
|
|
@@ -31,28 +31,48 @@ dependencies:
|
|
|
31
31
|
requirements:
|
|
32
32
|
- - "~>"
|
|
33
33
|
- !ruby/object:Gem::Version
|
|
34
|
-
version: 3.
|
|
34
|
+
version: 3.7.0
|
|
35
35
|
type: :runtime
|
|
36
36
|
prerelease: false
|
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
|
38
38
|
requirements:
|
|
39
39
|
- - "~>"
|
|
40
40
|
- !ruby/object:Gem::Version
|
|
41
|
-
version: 3.
|
|
41
|
+
version: 3.7.0
|
|
42
42
|
- !ruby/object:Gem::Dependency
|
|
43
43
|
name: ruby2ruby
|
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
|
45
45
|
requirements:
|
|
46
|
-
- - "
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: 2.1.1
|
|
49
|
+
- - "<"
|
|
47
50
|
- !ruby/object:Gem::Version
|
|
48
51
|
version: 2.3.0
|
|
49
52
|
type: :runtime
|
|
50
53
|
prerelease: false
|
|
51
54
|
version_requirements: !ruby/object:Gem::Requirement
|
|
52
55
|
requirements:
|
|
53
|
-
- - "
|
|
56
|
+
- - ">="
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: 2.1.1
|
|
59
|
+
- - "<"
|
|
54
60
|
- !ruby/object:Gem::Version
|
|
55
61
|
version: 2.3.0
|
|
62
|
+
- !ruby/object:Gem::Dependency
|
|
63
|
+
name: multi_json
|
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '1.2'
|
|
69
|
+
type: :runtime
|
|
70
|
+
prerelease: false
|
|
71
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '1.2'
|
|
56
76
|
- !ruby/object:Gem::Dependency
|
|
57
77
|
name: safe_yaml
|
|
58
78
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -194,6 +214,8 @@ files:
|
|
|
194
214
|
- lib/brakeman/report/config/remediation.yml
|
|
195
215
|
- lib/brakeman/report/ignore/config.rb
|
|
196
216
|
- lib/brakeman/report/ignore/interactive.rb
|
|
217
|
+
- lib/brakeman/report/initializers/faster_csv.rb
|
|
218
|
+
- lib/brakeman/report/initializers/multi_json.rb
|
|
197
219
|
- lib/brakeman/report/renderer.rb
|
|
198
220
|
- lib/brakeman/report/report_base.rb
|
|
199
221
|
- lib/brakeman/report/report_codeclimate.rb
|
|
@@ -245,9 +267,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
245
267
|
version: '0'
|
|
246
268
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
247
269
|
requirements:
|
|
248
|
-
- - "
|
|
270
|
+
- - ">="
|
|
249
271
|
- !ruby/object:Gem::Version
|
|
250
|
-
version:
|
|
272
|
+
version: '0'
|
|
251
273
|
requirements: []
|
|
252
274
|
rubyforge_project:
|
|
253
275
|
rubygems_version: 2.4.8
|