spektr 0.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 +7 -0
- data/.github/workflows/ci.yaml +32 -0
- data/.gitignore +9 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +3 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +134 -0
- data/Guardfile +45 -0
- data/LICENSE.txt +27 -0
- data/README.md +70 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bin/spektr +7 -0
- data/lib/spektr/app.rb +209 -0
- data/lib/spektr/checks/base.rb +151 -0
- data/lib/spektr/checks/basic_auth.rb +27 -0
- data/lib/spektr/checks/basic_auth_timing.rb +24 -0
- data/lib/spektr/checks/command_injection.rb +48 -0
- data/lib/spektr/checks/content_tag_xss.rb +54 -0
- data/lib/spektr/checks/cookie_serialization.rb +21 -0
- data/lib/spektr/checks/create_with.rb +27 -0
- data/lib/spektr/checks/csrf.rb +25 -0
- data/lib/spektr/checks/csrf_setting.rb +39 -0
- data/lib/spektr/checks/default_routes.rb +43 -0
- data/lib/spektr/checks/deserialize.rb +62 -0
- data/lib/spektr/checks/detailed_exceptions.rb +29 -0
- data/lib/spektr/checks/digest_dos.rb +28 -0
- data/lib/spektr/checks/dynamic_finders.rb +26 -0
- data/lib/spektr/checks/evaluation.rb +25 -0
- data/lib/spektr/checks/file_access.rb +38 -0
- data/lib/spektr/checks/file_disclosure.rb +25 -0
- data/lib/spektr/checks/filter_skipping.rb +29 -0
- data/lib/spektr/checks/header_dos.rb +20 -0
- data/lib/spektr/checks/i18n_xss.rb +20 -0
- data/lib/spektr/checks/json_encoding.rb +23 -0
- data/lib/spektr/checks/json_entity_escape.rb +30 -0
- data/lib/spektr/checks/json_parsing.rb +47 -0
- data/lib/spektr/checks/link_to_href.rb +35 -0
- data/lib/spektr/checks/mass_assignment.rb +42 -0
- data/lib/spektr/checks/send.rb +24 -0
- data/lib/spektr/checks/sqli.rb +52 -0
- data/lib/spektr/checks/xss.rb +49 -0
- data/lib/spektr/checks.rb +9 -0
- data/lib/spektr/cli.rb +53 -0
- data/lib/spektr/erubi.rb +78 -0
- data/lib/spektr/exp/assignment.rb +20 -0
- data/lib/spektr/exp/base.rb +32 -0
- data/lib/spektr/exp/const.rb +7 -0
- data/lib/spektr/exp/definition.rb +32 -0
- data/lib/spektr/exp/ivasign.rb +7 -0
- data/lib/spektr/exp/lvasign.rb +7 -0
- data/lib/spektr/exp/send.rb +135 -0
- data/lib/spektr/exp/xstr.rb +12 -0
- data/lib/spektr/processors/base.rb +80 -0
- data/lib/spektr/processors/class_processor.rb +25 -0
- data/lib/spektr/targets/base.rb +119 -0
- data/lib/spektr/targets/config.rb +6 -0
- data/lib/spektr/targets/controller.rb +74 -0
- data/lib/spektr/targets/model.rb +6 -0
- data/lib/spektr/targets/routes.rb +38 -0
- data/lib/spektr/targets/view.rb +34 -0
- data/lib/spektr/version.rb +3 -0
- data/lib/spektr/warning.rb +23 -0
- data/lib/spektr.rb +120 -0
- data/spektr.gemspec +49 -0
- metadata +362 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
module Spektr
|
2
|
+
class Checks
|
3
|
+
class FileDisclosure < Base
|
4
|
+
|
5
|
+
def initialize(app, target)
|
6
|
+
super
|
7
|
+
@name = "File existence disclosure"
|
8
|
+
@type = "Information Disclosure"
|
9
|
+
@targets = ["Spektr::Targets::Base"]
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
return unless super
|
14
|
+
config = @app.production_config.find_calls(:serve_static_assets=).first
|
15
|
+
if config && config.arguments.first.type == :true
|
16
|
+
warn! "root", self, nil, "File existence disclosure vulnerability"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def should_run?
|
21
|
+
app_version_between?("2.0.0", "2.3.18") || app_version_between?("3.0.0", "3.2.20") || app_version_between?("4.0.0", "4.0.11") || app_version_between?("4.1.0", "4.1.7")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Spektr
|
2
|
+
class Checks
|
3
|
+
class FilterSkipping < Base
|
4
|
+
def initialize(app, target)
|
5
|
+
super
|
6
|
+
@name = "Default routes filter skipping"
|
7
|
+
@type = "Default Routes"
|
8
|
+
@targets = ["Spektr::Targets::Routes"]
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
return unless super
|
13
|
+
calls = %w{ match get post put delete }.inject([]) do |memo, method|
|
14
|
+
memo.concat @target.find_calls(method.to_sym)
|
15
|
+
memo
|
16
|
+
end
|
17
|
+
calls.each do |call|
|
18
|
+
if !call.arguments.empty? && (call.arguments.first.name.include?(":action") or call.arguments.first.name.include?("*action"))
|
19
|
+
warn! @target, self, call.location, "CVE-2011-2929 Rails versions before 3.0.10 have a vulnerability which allows filters to be bypassed"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def should_run?
|
25
|
+
app_version_between?("3.0.0", "3.0.9")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Spektr
|
2
|
+
class Checks
|
3
|
+
class HeaderDos < Base
|
4
|
+
|
5
|
+
def initialize(app, target)
|
6
|
+
super
|
7
|
+
@name = "HTTP MIME type header DoS (CVE-2013-6414)"
|
8
|
+
@type = "Denial of Service"
|
9
|
+
@targets = ["Spektr::Targets::Base"]
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
return unless super
|
14
|
+
if app_version_between?("3.0.0", "3.2.15")
|
15
|
+
warn! "root", self, nil, "CVE_2013_6414"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Spektr
|
2
|
+
class Checks
|
3
|
+
class I18nXss < Base
|
4
|
+
|
5
|
+
def initialize(app, target)
|
6
|
+
super
|
7
|
+
@name = "XSS in i18n (CVE-2013-4491)"
|
8
|
+
@type = "Cross-Site Scripting"
|
9
|
+
@targets = ["Spektr::Targets::Base"]
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
return unless super
|
14
|
+
if app_version_between?("3.0.6", "3.2.15") || app_version_between?("4.0.0", "4.0.1")
|
15
|
+
warn! "root", self, nil, "I18n has a Cross-Site Scripting vulnerability (CVE_2013_4491)"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Spektr
|
2
|
+
class Checks
|
3
|
+
class JsonEncoding < Base
|
4
|
+
def initialize(app, target)
|
5
|
+
super
|
6
|
+
@name = "XSS by missing JSON encoding"
|
7
|
+
@type = "Cross-Site Scripting"
|
8
|
+
@targets = ["Spektr::Targets::Base", "Spektr::Targets::Controller", "Spektr::Targets::Routes", "Spektr::Targets::View"]
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
return unless super
|
13
|
+
if app_version_between?("4.1.0", "4.1.10") || app_version_between?("4.2.0", "4.2.1")
|
14
|
+
if calls = @target.find_calls(:to_json).any? || calls = @target.find_calls(:encode).any?
|
15
|
+
warn! @target, self, calls.first.location, "Cross-Site Scripting CVE_2015_3226"
|
16
|
+
else
|
17
|
+
warn! "root", self, nil, "Cross-Site Scripting CVE_2015_3226"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Spektr
|
2
|
+
class Checks
|
3
|
+
class JsonEntityEscape < Base
|
4
|
+
|
5
|
+
def initialize(app, target)
|
6
|
+
super
|
7
|
+
@name = "HTML escaping is disabled for JSON output"
|
8
|
+
@type = "Cross-Site Scripting"
|
9
|
+
@targets = ["Spektr::Targets::Config", "Spektr::Targets::Base"]
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
return unless super
|
14
|
+
if @app.production_config
|
15
|
+
config = @app.production_config.find_calls(:escape_html_entities_in_json=).first
|
16
|
+
end
|
17
|
+
if config and config.receiver.expanded == "config.active_support" && config.arguments.first.type == :false
|
18
|
+
warn! @app.production_config.path, self, nil, "HTML entities in JSON are not escaped by default"
|
19
|
+
end
|
20
|
+
['ActiveSupport', 'ActiveSupport.JSON.Encoding'].each do |receiver|
|
21
|
+
calls = @target.find_calls(:escape_html_entities_in_json=, receiver)
|
22
|
+
if calls.any?
|
23
|
+
warn! @target, self, calls.first.location, "HTML entities in JSON are not escaped by default"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Spektr
|
2
|
+
class Checks
|
3
|
+
class JsonParsing < Base
|
4
|
+
def initialize(app, target)
|
5
|
+
super
|
6
|
+
@name = "JSON parsing vulnerability"
|
7
|
+
@type = "Remote Code Execution"
|
8
|
+
@targets = ["Spektr::Targets::Base"]
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
return unless super
|
13
|
+
check_cve_2013_0333
|
14
|
+
check_cve_2013_0269
|
15
|
+
end
|
16
|
+
|
17
|
+
def check_cve_2013_0333
|
18
|
+
return unless app_version_between?("0.0.0", "2.3.15") || app_version_between?("3.0.0", "3.0.19")
|
19
|
+
if @app.has_gem?("yajl")
|
20
|
+
warn! "root", self, nil, "Remote Code Execution CVE_2013_0333"
|
21
|
+
end
|
22
|
+
uses_json_gem?
|
23
|
+
end
|
24
|
+
|
25
|
+
def uses_json_gem?
|
26
|
+
@target.find_calls(:backend=).each do |call|
|
27
|
+
if call.receiver.expanded == "ActiveSupport.JSON" && call.arguments.first&.name == :JSONGem
|
28
|
+
warn! @target, self, call.location, "Remote Code Execution CVE_2013_0333"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def check_cve_2013_0269
|
34
|
+
["json", "json_pure"].each do |gem_name|
|
35
|
+
if g = @app.gem_specs&.find { |g| g.name == gem_name }
|
36
|
+
if version_between?("1.7.0", "1.7.6", g.version)
|
37
|
+
warn! "Gemfile", self, nil, "Unsafe Object Creation Vulnerability in the #{g.name} gem"
|
38
|
+
end
|
39
|
+
if version_between?("0", "1.5.4", g.version) || version_between?("1.6.0", "1.6.7", g.version)
|
40
|
+
warn! "Gemfile", self, nil, "Unsafe Object Creation Vulnerability in the #{g.name} gem"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Spektr
|
2
|
+
class Checks
|
3
|
+
class LinkToHref < Base
|
4
|
+
def initialize(app, target)
|
5
|
+
super
|
6
|
+
@name = "XSS in href param of link_to"
|
7
|
+
@type = "Cross-Site Scripting"
|
8
|
+
@targets = ["Spektr::Targets::Base", "Spektr::Targets::Controller", "Spektr::Targets::View"]
|
9
|
+
end
|
10
|
+
|
11
|
+
# TODO: check for user supplied model attributes too
|
12
|
+
def run
|
13
|
+
return unless super
|
14
|
+
block_locations = []
|
15
|
+
@target.find_calls_with_block(:link_to).each do |call|
|
16
|
+
block_locations << call.location
|
17
|
+
next unless call.arguments.first
|
18
|
+
::Spektr.logger.debug "#{@target.path} #{call.location.line} #{call.arguments.first.inspect}"
|
19
|
+
if user_input? call.arguments.first.type, call.arguments.first.name, call.arguments.first.ast, call.arguments.first
|
20
|
+
warn! @target, self, call.location, "Cross-Site Scripting: Unsafe user supplied value in link_to"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
@target.find_calls(:link_to).each do |call|
|
25
|
+
next if block_locations.include? call.location
|
26
|
+
::Spektr.logger.debug "#{@target.path} #{call.location.line} #{call.arguments[1].inspect}"
|
27
|
+
next unless call.arguments[1] || call.arguments[1]&.name =~ /_url$|_path$/
|
28
|
+
if user_input? call.arguments[1].type, call.arguments[1].name, call.arguments[1].ast, call.arguments[1]
|
29
|
+
warn! @target, self, call.location, "Cross-Site Scripting: Unsafe user supplied value in link_to"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Spektr
|
2
|
+
class Checks
|
3
|
+
class MassAssignment < Base
|
4
|
+
|
5
|
+
# TODO: Make this better
|
6
|
+
def initialize(app, target)
|
7
|
+
super
|
8
|
+
@name = "Mass Assignment"
|
9
|
+
@type = "Input Validation"
|
10
|
+
@targets = ["Spektr::Targets::Controller"]
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
return unless super
|
15
|
+
model_names = @app.models.collect(&:name)
|
16
|
+
calls = []
|
17
|
+
model_names.each do |receiver|
|
18
|
+
[:new, :build, :create].each do |method|
|
19
|
+
calls.concat @target.find_calls(method, receiver)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
calls.each do |call|
|
23
|
+
argument = call.arguments.first
|
24
|
+
next if argument.nil?
|
25
|
+
::Spektr.logger.debug "Mass assignment check at #{call.location.line}"
|
26
|
+
if user_input?(argument.type, argument.name, call.ast)
|
27
|
+
# we check for permit! separately
|
28
|
+
next if argument.ast.children[1] == :permit!
|
29
|
+
# check for permit with arguments
|
30
|
+
next if argument.ast.children[1] == :permit && argument.ast.children[2]
|
31
|
+
warn! @target, self, call.location, "Mass assignment"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
@target.find_calls(:permit!).each do |call|
|
35
|
+
if call.arguments.none?
|
36
|
+
warn! @target, self, call.location, "permit! allows any keys, use it with caution!", :medium
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Spektr
|
2
|
+
class Checks
|
3
|
+
class Send < Base
|
4
|
+
def initialize(app, target)
|
5
|
+
super
|
6
|
+
@name = "Dangerous send"
|
7
|
+
@type = "Dangerous send"
|
8
|
+
@targets = ["Spektr::Targets::Base", "Spektr::Targets::Model", "Spektr::Targets::Controller", "Spektr::Targets::Routes", "Spektr::Targets::View"]
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
return unless super
|
13
|
+
[:send, :try, :__send__, :public_send].each do |method|
|
14
|
+
@target.find_calls(method).each do |call|
|
15
|
+
argument = call.arguments.first
|
16
|
+
if user_input?(argument.type, argument.name, argument.ast)
|
17
|
+
warn! @target, self, call.location, "User supplied value in send"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Spektr
|
2
|
+
class Checks
|
3
|
+
class Sqli < Base
|
4
|
+
def initialize(app, target)
|
5
|
+
super
|
6
|
+
@name = "SQL Injection"
|
7
|
+
@name = "SQL Injection"
|
8
|
+
@targets = ["Spektr::Targets::Base", "Spektr::Targets::Controller", "Spektr::Targets::Model"]
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
return unless super
|
13
|
+
|
14
|
+
[
|
15
|
+
:average, :count, :maximum, :minimum, :sum, :exists?,
|
16
|
+
:find_by, :find_by!, :find_or_create_by, :find_or_create_by!,
|
17
|
+
:find_or_initialize_by, :from, :group, :having, :join, :lock,
|
18
|
+
:where, :not, :select, :rewhere, :reselect, :update_all
|
19
|
+
|
20
|
+
].each do |m|
|
21
|
+
@target.find_calls(m).each do |call|
|
22
|
+
check_argument(call.arguments.first, m, call)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
[:calculate].each do |m|
|
26
|
+
@target.find_calls(m).each do |call|
|
27
|
+
check_argument(call.arguments[1], m, call)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
[:delete_by, :destroy_by].each do |m|
|
32
|
+
@target.find_calls(m).each do |call|
|
33
|
+
if call.arguments.first
|
34
|
+
check_argument(call.arguments.first, m, call)
|
35
|
+
end
|
36
|
+
call.options.values.each do |option|
|
37
|
+
check_argument(@target.ast_to_exp(option.key), m, call)
|
38
|
+
check_argument(@target.ast_to_exp(option.value), m, call)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def check_argument(argument, method, call)
|
45
|
+
return if argument.nil?
|
46
|
+
if user_input?(argument.type, argument.name, argument.ast, argument)
|
47
|
+
warn! @target, self, call.location, "Possible SQL Injection at #{method}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Spektr
|
2
|
+
class Checks
|
3
|
+
class Xss < Base
|
4
|
+
def initialize(app, target)
|
5
|
+
super
|
6
|
+
@name = "XSS"
|
7
|
+
@type = "Cross-Site Scripting"
|
8
|
+
@targets = ["Spektr::Targets::Base", "Spektr::Targets::Controller", "Spektr::Targets::View"]
|
9
|
+
end
|
10
|
+
|
11
|
+
# TODO: tests for haml, xml, js
|
12
|
+
# TODO: add check for raw calls
|
13
|
+
def run
|
14
|
+
return unless super
|
15
|
+
calls = @target.find_calls(:safe_expr_append=)
|
16
|
+
calls.concat(@target.find_calls(:raw))
|
17
|
+
calls.each do |call|
|
18
|
+
call.arguments.each do |argument|
|
19
|
+
if user_input?(argument.type, argument.name, argument.ast)
|
20
|
+
warn! @target, self, call.location, "Cross-Site Scripting: Unescaped user input"
|
21
|
+
end
|
22
|
+
if model_attribute?(argument)
|
23
|
+
warn! @target, self, call.location, "Cross-Site Scripting: Unescaped model attribute #{argument.name}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
calls.each do |call|
|
28
|
+
call.arguments.each do |argument|
|
29
|
+
if user_input?(argument.type, argument.name, argument.ast)
|
30
|
+
warn! @target, self, call.location, "Cross-Site Scripting: Unescaped user input"
|
31
|
+
end
|
32
|
+
if model_attribute?(argument)
|
33
|
+
warn! @target, self, call.location, "Cross-Site Scripting: Unescaped model attribute #{argument.name}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
calls = @target.find_calls(:html_safe)
|
38
|
+
calls.each do |call|
|
39
|
+
if user_input?(call.receiver.type, call.receiver.name, call.receiver.ast, call.receiver)
|
40
|
+
warn! @target, self, call.location, "Cross-Site Scripting: Unescaped user input"
|
41
|
+
end
|
42
|
+
if model_attribute?(call.receiver)
|
43
|
+
warn! @target, self, call.location, "Cross-Site Scripting: Unescaped model attribute #{call.receiver.name}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/spektr/cli.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'tty/option'
|
2
|
+
require 'json'
|
3
|
+
module Spektr
|
4
|
+
class Cli
|
5
|
+
include TTY::Option
|
6
|
+
usage do
|
7
|
+
program 'Spektr'
|
8
|
+
command 'scan'
|
9
|
+
desc 'Find vulnerabilities in ruby code'
|
10
|
+
end
|
11
|
+
|
12
|
+
argument :root do
|
13
|
+
optional
|
14
|
+
desc 'Path to application root'
|
15
|
+
end
|
16
|
+
|
17
|
+
flag :output_format do
|
18
|
+
long '--output_format string'
|
19
|
+
desc 'output format terminal or json'
|
20
|
+
default 'terminal'
|
21
|
+
end
|
22
|
+
|
23
|
+
flag :check do
|
24
|
+
long '--check string'
|
25
|
+
desc 'run this single check'
|
26
|
+
end
|
27
|
+
|
28
|
+
flag :debug do
|
29
|
+
long '--debug'
|
30
|
+
short '-d'
|
31
|
+
desc 'output debug logs to STDOUT'
|
32
|
+
end
|
33
|
+
|
34
|
+
flag :help do
|
35
|
+
short '-h'
|
36
|
+
long '--help'
|
37
|
+
desc 'Print usage'
|
38
|
+
end
|
39
|
+
|
40
|
+
def scan
|
41
|
+
if params[:help]
|
42
|
+
print help
|
43
|
+
exit
|
44
|
+
else
|
45
|
+
report = Spektr.run(params[:root], params[:output_format], params[:debug], params[:check])
|
46
|
+
case params[:output_format]
|
47
|
+
when 'json'
|
48
|
+
puts JSON.pretty_generate report
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/spektr/erubi.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require "erubi"
|
2
|
+
# This is a copy of module ActionView::Template::Handlers::ERB::Erubi
|
3
|
+
module Spektr
|
4
|
+
class Erubi < ::Erubi::Engine
|
5
|
+
def initialize(input, properties = {})
|
6
|
+
@newline_pending = 0
|
7
|
+
|
8
|
+
# Dup properties so that we don't modify argument
|
9
|
+
properties = Hash[properties]
|
10
|
+
properties[:preamble] = ""
|
11
|
+
properties[:postamble] = "@output_buffer.to_s"
|
12
|
+
properties[:bufvar] = "@output_buffer"
|
13
|
+
properties[:escapefunc] = ""
|
14
|
+
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def evaluate(action_view_erb_handler_context)
|
19
|
+
src = @src
|
20
|
+
view = Class.new(ActionView::Base) {
|
21
|
+
include action_view_erb_handler_context._routes.url_helpers
|
22
|
+
class_eval("define_method(:_template) { |local_assigns, output_buffer| #{src} }", defined?(@filename) ? @filename : "(erubi)", 0)
|
23
|
+
}.empty
|
24
|
+
view._run(:_template, nil, {}, ActionView::OutputBuffer.new)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def add_text(text)
|
29
|
+
return if text.empty?
|
30
|
+
|
31
|
+
if text == "\n"
|
32
|
+
@newline_pending += 1
|
33
|
+
else
|
34
|
+
src << "@output_buffer.safe_append='"
|
35
|
+
src << "\n" * @newline_pending if @newline_pending > 0
|
36
|
+
src << text.gsub(/['\\]/, '\\\\\&')
|
37
|
+
src << "'.freeze;"
|
38
|
+
|
39
|
+
@newline_pending = 0
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
|
44
|
+
|
45
|
+
def add_expression(indicator, code)
|
46
|
+
flush_newline_if_pending(src)
|
47
|
+
|
48
|
+
if (indicator == "==") || @escape
|
49
|
+
src << "@output_buffer.safe_expr_append="
|
50
|
+
else
|
51
|
+
src << "@output_buffer.append="
|
52
|
+
end
|
53
|
+
|
54
|
+
if BLOCK_EXPR.match?(code)
|
55
|
+
src << " " << code
|
56
|
+
else
|
57
|
+
src << "(" << code << ");"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def add_code(code)
|
62
|
+
flush_newline_if_pending(src)
|
63
|
+
super
|
64
|
+
end
|
65
|
+
|
66
|
+
def add_postamble(_)
|
67
|
+
flush_newline_if_pending(src)
|
68
|
+
super
|
69
|
+
end
|
70
|
+
|
71
|
+
def flush_newline_if_pending(src)
|
72
|
+
if @newline_pending > 0
|
73
|
+
src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
|
74
|
+
@newline_pending = 0
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Spektr
|
2
|
+
module Exp
|
3
|
+
module Assignment
|
4
|
+
def user_input?
|
5
|
+
if ast.children[1].type == :send
|
6
|
+
_send = Send.new(ast.children[1])
|
7
|
+
name = if _send.receiver && _send.receiver.type == :send
|
8
|
+
_send.receiver.name
|
9
|
+
else
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
if [:params, :cookies, :request].include? name
|
13
|
+
return true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Spektr
|
2
|
+
module Exp
|
3
|
+
class Base
|
4
|
+
attr_accessor :ast, :name, :type, :options, :arguments, :body, :location
|
5
|
+
|
6
|
+
def initialize(ast)
|
7
|
+
@ast = ast
|
8
|
+
@type = ast.type
|
9
|
+
@location = ast.location
|
10
|
+
@name = ast.children.first
|
11
|
+
@options = {}
|
12
|
+
@arguments = []
|
13
|
+
@body = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def send?
|
17
|
+
is_a? Send
|
18
|
+
end
|
19
|
+
|
20
|
+
include AST::Processor::Mixin
|
21
|
+
|
22
|
+
def process(ast)
|
23
|
+
return unless ast.respond_to?(:to_ast)
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def handler_missing(node)
|
28
|
+
# puts "handler missing for #{node.type}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Spektr
|
2
|
+
module Exp
|
3
|
+
class Definition < Base
|
4
|
+
attr_accessor :private, :protected
|
5
|
+
|
6
|
+
def initialize(ast)
|
7
|
+
super
|
8
|
+
process @ast.children
|
9
|
+
end
|
10
|
+
|
11
|
+
def process(ast)
|
12
|
+
ast&.each do |node|
|
13
|
+
next unless Parser::AST::Node === node
|
14
|
+
case node.type
|
15
|
+
when :args
|
16
|
+
node.children.each do |argument|
|
17
|
+
@arguments << argument.children.first
|
18
|
+
end
|
19
|
+
when :begin
|
20
|
+
process(node.children)
|
21
|
+
when :lvasgn
|
22
|
+
@body << Lvasign.new(node)
|
23
|
+
when :ivasgn
|
24
|
+
@body << Ivasign.new(node)
|
25
|
+
when :send
|
26
|
+
@body << Send.new(node)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|