pluginscan 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.gitlab-ci.yml +16 -0
- data/.rspec +3 -0
- data/.rubocop.yml +46 -0
- data/.rubocop_todo.yml +36 -0
- data/CHANGELOG.md +89 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +90 -0
- data/README.md +56 -0
- data/Rakefile +2 -0
- data/TODO.md +8 -0
- data/bin/pluginscan +53 -0
- data/lib/file_creator.rb +18 -0
- data/lib/pluginscan.rb +69 -0
- data/lib/pluginscan/error.rb +9 -0
- data/lib/pluginscan/error_printer.rb +17 -0
- data/lib/pluginscan/file_finder.rb +42 -0
- data/lib/pluginscan/printer.rb +14 -0
- data/lib/pluginscan/reports/cloc_report.rb +27 -0
- data/lib/pluginscan/reports/cloc_report/cloc.rb +21 -0
- data/lib/pluginscan/reports/cloc_report/cloc_printer.rb +42 -0
- data/lib/pluginscan/reports/cloc_report/cloc_scanner.rb +41 -0
- data/lib/pluginscan/reports/cloc_report/system_cloc.rb +33 -0
- data/lib/pluginscan/reports/issues_report.rb +24 -0
- data/lib/pluginscan/reports/issues_report/error_list_printer.rb +99 -0
- data/lib/pluginscan/reports/issues_report/issue_checks.rb +382 -0
- data/lib/pluginscan/reports/issues_report/issue_checks/check.rb +55 -0
- data/lib/pluginscan/reports/issues_report/issue_checks/comment_checker.rb +13 -0
- data/lib/pluginscan/reports/issues_report/issue_checks/function_check.rb +32 -0
- data/lib/pluginscan/reports/issues_report/issue_checks/variable_check.rb +14 -0
- data/lib/pluginscan/reports/issues_report/issue_checks/variable_safety_checker.rb +112 -0
- data/lib/pluginscan/reports/issues_report/issues_models/check_findings.rb +29 -0
- data/lib/pluginscan/reports/issues_report/issues_models/issues.rb +31 -0
- data/lib/pluginscan/reports/issues_report/issues_printer.rb +34 -0
- data/lib/pluginscan/reports/issues_report/issues_printer/check_findings_printer.rb +37 -0
- data/lib/pluginscan/reports/issues_report/issues_printer/file_issues_printer.rb +36 -0
- data/lib/pluginscan/reports/issues_report/issues_printer/finding_printer.rb +38 -0
- data/lib/pluginscan/reports/issues_report/issues_printer_factory.rb +19 -0
- data/lib/pluginscan/reports/issues_report/issues_scanner.rb +49 -0
- data/lib/pluginscan/reports/issues_report/issues_scanner/file_issues_scanner.rb +39 -0
- data/lib/pluginscan/reports/issues_report/issues_scanner/line_issues_scanner.rb +15 -0
- data/lib/pluginscan/reports/issues_report/issues_scanner/utf8_checker.rb +14 -0
- data/lib/pluginscan/reports/sloccount_report.rb +26 -0
- data/lib/pluginscan/reports/sloccount_report/sloccount.rb +19 -0
- data/lib/pluginscan/reports/sloccount_report/sloccount_printer.rb +22 -0
- data/lib/pluginscan/reports/sloccount_report/sloccount_scanner.rb +86 -0
- data/lib/pluginscan/reports/vulnerability_report.rb +28 -0
- data/lib/pluginscan/reports/vulnerability_report/advisories_api.rb +23 -0
- data/lib/pluginscan/reports/vulnerability_report/vulnerabilities_printer.rb +55 -0
- data/lib/pluginscan/reports/vulnerability_report/vulnerability_scanner.rb +17 -0
- data/lib/pluginscan/reports/vulnerability_report/wp_vuln_db_api.rb +77 -0
- data/lib/pluginscan/version.rb +3 -0
- data/pluginscan.gemspec +31 -0
- data/spec/acceptance/cloc_spec.rb +54 -0
- data/spec/acceptance/create_error_list_file_spec.rb +29 -0
- data/spec/acceptance/issues_spec.rb +197 -0
- data/spec/acceptance/pluginscan_spec.rb +18 -0
- data/spec/acceptance/sloccount_spec.rb +39 -0
- data/spec/acceptance/vulnerabilities_spec.rb +57 -0
- data/spec/acceptance_spec_helper.rb +10 -0
- data/spec/checks_examples_spec.rb +352 -0
- data/spec/file_creator_spec.rb +51 -0
- data/spec/pluginscan/cloc_scanner/cloc_scanner_spec.rb +64 -0
- data/spec/pluginscan/cloc_scanner/cloc_spec.rb +30 -0
- data/spec/pluginscan/file_finder_spec.rb +91 -0
- data/spec/pluginscan/issues_scanner/check_findings_spec.rb +22 -0
- data/spec/pluginscan/issues_scanner/error_list_printer_ignores_spec.rb +35 -0
- data/spec/pluginscan/issues_scanner/error_list_printer_spec.rb +42 -0
- data/spec/pluginscan/issues_scanner/file_issues_scanner_spec.rb +25 -0
- data/spec/pluginscan/issues_scanner/issues_printer_factory_spec.rb +9 -0
- data/spec/pluginscan/issues_scanner/issues_spec.rb +55 -0
- data/spec/pluginscan/issues_scanner/variable_check_spec.rb +13 -0
- data/spec/pluginscan/issues_scanner/variable_safety_checker_spec.rb +81 -0
- data/spec/pluginscan/issues_scanner_spec.rb +21 -0
- data/spec/pluginscan/sloccount_scanner/sloccount_scanner_spec.rb +95 -0
- data/spec/pluginscan/sloccount_scanner/sloccount_spec.rb +72 -0
- data/spec/pluginscan/vulnerability_scanner_spec.rb +96 -0
- data/spec/process_spec_helper.rb +6 -0
- data/spec/spec_helper.rb +70 -0
- data/spec/support/acceptance_helpers.rb +68 -0
- data/spec/support/file_helpers.rb +35 -0
- data/spec/support/heredoc_helper.rb +7 -0
- data/spec/support/process_helpers.rb +25 -0
- data/spec/support/shared_examples_for_issue_checks.rb +31 -0
- data/spec/support/vcr_helper.rb +6 -0
- data/vcr_cassettes/wpvulndb/relevanssi.yml +78 -0
- metadata +342 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
module Pluginscan
|
2
|
+
# Responsible for scanning one line of a file for issues of a certain type
|
3
|
+
class LineIssuesScanner
|
4
|
+
def initialize(check)
|
5
|
+
@check = check
|
6
|
+
end
|
7
|
+
|
8
|
+
def scan(line, lineno)
|
9
|
+
@check.run(line).map do |match|
|
10
|
+
ignored = @check.ignore?(match, line)
|
11
|
+
Finding.new(lineno, line, match, ignored)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Pluginscan
|
2
|
+
# Responsible for checking a file for utf-8 validity
|
3
|
+
class UTF8Checker
|
4
|
+
# TODO: This returns nil if the file was valid. Returning nil is bad.
|
5
|
+
def check(file_contents)
|
6
|
+
# Check file contents are valid UTF-8
|
7
|
+
file_contents.force_encoding('utf-8')
|
8
|
+
return if file_contents.valid_encoding?
|
9
|
+
CheckFindings.new(
|
10
|
+
Check.new(name: 'Encoding', message: 'invalid UTF-8')
|
11
|
+
)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'pluginscan/reports/sloccount_report/sloccount'
|
2
|
+
require 'pluginscan/reports/sloccount_report/sloccount_scanner'
|
3
|
+
require 'pluginscan/reports/sloccount_report/sloccount_printer'
|
4
|
+
require 'pluginscan/error_printer'
|
5
|
+
|
6
|
+
module Pluginscan
|
7
|
+
module Reports
|
8
|
+
module SLOCCountReport
|
9
|
+
def self.print(plugin_directory, printer, error_printer)
|
10
|
+
sloccount = SLOCCountScanner.new.scan(plugin_directory)
|
11
|
+
printer.print(sloccount)
|
12
|
+
|
13
|
+
rescue SLOCCountScanner::Unavailable
|
14
|
+
error_printer.print("The 'sloccount' command is unavailable. Is it installed and in your path?")
|
15
|
+
rescue SLOCCountScanner::Exception => e
|
16
|
+
error_printer.print("An error occurred while calculating the SLOCCount:\n #{e.message}")
|
17
|
+
rescue StandardError => e
|
18
|
+
# We don't want an error with CLOC to interrupt the rest of pluginscan
|
19
|
+
# TODO: not sure this is sensible
|
20
|
+
error_printer.print("An error occurred while calculating the SLOCCount:\n #{e.message}")
|
21
|
+
else
|
22
|
+
return true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# Responsible for accessing specific data in the output of the `sloccount` command
|
2
|
+
class SLOCCount
|
3
|
+
def initialize(sloccount_output)
|
4
|
+
@sloccount_output = sloccount_output
|
5
|
+
end
|
6
|
+
|
7
|
+
def total
|
8
|
+
total_regex = /^Total Physical Source Lines of Code.*= ([\d\,]+)/
|
9
|
+
# Numbers in sloccount are displayed with commas e.g. 1,791
|
10
|
+
@sloccount_output.match(total_regex)[1].delete(',').to_i
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# TODO: should be ZeroSLOCCount? More descriptive, but less obvious it's null object pattern
|
15
|
+
class NullSLOCCount
|
16
|
+
def total
|
17
|
+
0
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'pluginscan/printer'
|
2
|
+
|
3
|
+
module Pluginscan
|
4
|
+
class SLOCCountPrinter < Printer
|
5
|
+
def print(sloccount)
|
6
|
+
print_headline
|
7
|
+
print_results(sloccount)
|
8
|
+
print_blank_line
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def print_headline
|
14
|
+
@output.puts "Total sloccount (from David A. Wheeler's 'SLOCCount' tool):".color(:blue)
|
15
|
+
end
|
16
|
+
|
17
|
+
def print_results(sloccount)
|
18
|
+
total = sloccount.total.to_s.color(:red)
|
19
|
+
@output.puts " #{total} source lines of code"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
# Responsible for running the `sloccount` system command and handling any resulting errors
|
4
|
+
# The SLOCCount project is at http://www.dwheeler.com/sloccount/ and can be installed on OSX using homebrew:
|
5
|
+
# brew install sloccount
|
6
|
+
class SLOCCountScanner
|
7
|
+
class Exception < StandardError; end
|
8
|
+
class ArgumentError < RuntimeError; end
|
9
|
+
class Unavailable < RuntimeError; end
|
10
|
+
class NoInput < RuntimeError; end
|
11
|
+
class NoDirectory < RuntimeError; end
|
12
|
+
|
13
|
+
def initialize(system_sloccount = SystemSLOCCount.instance)
|
14
|
+
@system_sloccount = system_sloccount
|
15
|
+
end
|
16
|
+
|
17
|
+
def scan(directory)
|
18
|
+
fail ArgumentError, "directory must must be a string (or quack like a string)" unless directory.respond_to?(:to_str)
|
19
|
+
fail Unavailable, "The 'sloccount' command is unavailable. Is it installed and in your path?" unless @system_sloccount.available?
|
20
|
+
fail NoDirectory, "No such directory: '#{directory}'" unless Dir.exist?(directory)
|
21
|
+
|
22
|
+
result, status = @system_sloccount.call(directory)
|
23
|
+
|
24
|
+
if status.success?
|
25
|
+
SLOCCount.new(result)
|
26
|
+
else
|
27
|
+
check_for_errors(result) { NullSLOCCount.new }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def check_for_errors(result)
|
34
|
+
case result
|
35
|
+
when /SLOC total is zero, no further analysis performed/
|
36
|
+
yield
|
37
|
+
|
38
|
+
when /Error: You must provide a directory or directories of source code/
|
39
|
+
# Because of the check above, it shouldn't be possible to get here
|
40
|
+
raise NoInput, "sloccount requires a directory or directories of source code"
|
41
|
+
else
|
42
|
+
raise Exception, "sloccount raised an error we didn't recognise. Here's the output:\n#{result}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Responsible for isolating the system calls to run SLOCCount
|
47
|
+
class SystemSLOCCount
|
48
|
+
require 'singleton'
|
49
|
+
include Singleton
|
50
|
+
|
51
|
+
COMMAND_NAME = "sloccount".freeze
|
52
|
+
|
53
|
+
def available?
|
54
|
+
_result, status = system_which_sloccount
|
55
|
+
status.success?
|
56
|
+
end
|
57
|
+
|
58
|
+
def call(directory)
|
59
|
+
# DANGER!!! calling system command
|
60
|
+
# also: danger! not covered by specs
|
61
|
+
|
62
|
+
# Should be safe from injection because of the `Dir.exist?` check.
|
63
|
+
Open3.capture2("#{COMMAND_NAME} #{directory} 2> #{error_log_name}")
|
64
|
+
|
65
|
+
ensure
|
66
|
+
delete_empty_error_log
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def system_which_sloccount
|
72
|
+
# DANGER!!! calling system command
|
73
|
+
# also: danger! not covered by specs
|
74
|
+
|
75
|
+
Open3.capture2("which #{COMMAND_NAME}")
|
76
|
+
end
|
77
|
+
|
78
|
+
def error_log_name
|
79
|
+
"#{COMMAND_NAME}_error.log"
|
80
|
+
end
|
81
|
+
|
82
|
+
def delete_empty_error_log
|
83
|
+
File.delete(error_log_name) if File.zero?(error_log_name)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'pluginscan/reports/vulnerability_report/advisories_api'
|
2
|
+
require 'pluginscan/reports/vulnerability_report/wp_vuln_db_api'
|
3
|
+
require 'pluginscan/reports/vulnerability_report/vulnerability_scanner'
|
4
|
+
require 'pluginscan/reports/vulnerability_report/vulnerabilities_printer'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
module Pluginscan
|
8
|
+
module Reports
|
9
|
+
module VulnerabilityReport
|
10
|
+
def self.print(plugin_directory, printer, error_printer)
|
11
|
+
plugin_slug = get_plugin_slug(plugin_directory)
|
12
|
+
advisories = VulnerabilityScanner.new.scan(plugin_slug)
|
13
|
+
|
14
|
+
printer.print(advisories, plugin_slug)
|
15
|
+
true
|
16
|
+
rescue WPVulnDB::APIError => e
|
17
|
+
error_printer.print(e.message)
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.get_plugin_slug(plugin_directory)
|
22
|
+
# Expanding the path handles '.' and '..' etc.
|
23
|
+
full_path = File.expand_path(plugin_directory)
|
24
|
+
full_path.split('/').last
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
|
3
|
+
module Pluginscan
|
4
|
+
# Responsible for calling an api endpoint
|
5
|
+
# and re-raising ruby errors with more information
|
6
|
+
class AdvisoriesAPI
|
7
|
+
class Error < StandardError; end
|
8
|
+
class ConnectionError < Error; end
|
9
|
+
|
10
|
+
def initialize(api_name:, timeout:)
|
11
|
+
@api_name = api_name
|
12
|
+
@timeout = timeout
|
13
|
+
end
|
14
|
+
|
15
|
+
def get(uri)
|
16
|
+
HTTParty.get(uri, timeout: @timeout)
|
17
|
+
rescue SocketError
|
18
|
+
raise(ConnectionError, "Couldn't connect to #{@api_name} (SocketError)")
|
19
|
+
rescue Net::OpenTimeout
|
20
|
+
raise(ConnectionError, "Connection to #{@api_name} timed out after #{@timeout} seconds")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Pluginscan
|
2
|
+
class VulnerabilitiesPrinter < Printer
|
3
|
+
def print(advisories, plugin_slug)
|
4
|
+
raise ArgumentError, "Can't print a nil list of advisories" if advisories.nil?
|
5
|
+
|
6
|
+
print_headline(advisories, plugin_slug)
|
7
|
+
|
8
|
+
advisories.reverse.each do |advisory|
|
9
|
+
print_advisory(advisory)
|
10
|
+
end
|
11
|
+
|
12
|
+
print_blank_line
|
13
|
+
end
|
14
|
+
|
15
|
+
private def print_headline(advisories, plugin_slug)
|
16
|
+
if advisories.any?
|
17
|
+
@output.puts "#{advisories.count} advisories were found for '#{plugin_slug}':".color(:blue)
|
18
|
+
else
|
19
|
+
@output.puts "No advisories were found for '#{plugin_slug}'".color(:blue)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private def print_advisory(advisory)
|
24
|
+
printer = VulnerabilityPrinter.new(@output)
|
25
|
+
printer.print(advisory)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class VulnerabilityPrinter < Printer
|
30
|
+
def print(advisory)
|
31
|
+
title = highlight_version_number(advisory.title)
|
32
|
+
date = format_date(advisory.date)
|
33
|
+
fixed = fixed_data(advisory.fixed_in)
|
34
|
+
@output.puts " #{date} #{title} #{fixed}"
|
35
|
+
@output.puts " #{advisory.url}"
|
36
|
+
end
|
37
|
+
|
38
|
+
private def highlight_version_number(title)
|
39
|
+
version_number_regex = '([\<\>\=]+\ )?[\d\.]+'
|
40
|
+
title.gsub(
|
41
|
+
/(?<version>#{version_number_regex})/,
|
42
|
+
'\k<version>'.color(:yellow)
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
private def format_date(date)
|
47
|
+
date.strftime('%Y-%m-%d').color(:green)
|
48
|
+
end
|
49
|
+
|
50
|
+
private def fixed_data(fixed_version)
|
51
|
+
return "(no fixed version!)".color(:red) if fixed_version.nil?
|
52
|
+
"(fixed in #{fixed_version})".color(:red)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Pluginscan
|
2
|
+
# Responsible for calling out to an API to see if any advisories
|
3
|
+
# have been published about this plugin
|
4
|
+
class VulnerabilityScanner
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
def initialize(advisories_api = WPVulnDB::API.new, response_handler = WPVulnDB::APIResponseHandler.new)
|
8
|
+
@advisories_api = advisories_api
|
9
|
+
@response_handler = response_handler
|
10
|
+
end
|
11
|
+
|
12
|
+
def scan(plugin_slug)
|
13
|
+
response = @advisories_api.get_plugin_advisories(plugin_slug)
|
14
|
+
@response_handler.call(response, plugin_slug)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Pluginscan
|
2
|
+
# Responsible for calling the WPVulnDB API
|
3
|
+
module WPVulnDB
|
4
|
+
class APIError < StandardError; end
|
5
|
+
class AccessDeniedError < APIError; end
|
6
|
+
class UnexpectedJSONError < APIError; end
|
7
|
+
|
8
|
+
class API
|
9
|
+
def initialize
|
10
|
+
@api = AdvisoriesAPI.new(api_name: 'wpvulndb', timeout: 10)
|
11
|
+
end
|
12
|
+
|
13
|
+
def get_plugin_advisories(plugin_slug)
|
14
|
+
@api.get(uri(plugin_slug))
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def uri(plugin_slug)
|
20
|
+
"#{base_uri}/plugins/#{plugin_slug}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def base_uri
|
24
|
+
'https://wpvulndb.com/api/v2'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class APIResponseHandler
|
29
|
+
def call(response, plugin_slug)
|
30
|
+
case response.code
|
31
|
+
when 200
|
32
|
+
DataMapper.new.call(response.parsed_response, plugin_slug)
|
33
|
+
when 404
|
34
|
+
[]
|
35
|
+
when 403
|
36
|
+
raise(AccessDeniedError, "We got blocked by wpvulndb for suspicious activity :( Contact team@wpvulndb.com")
|
37
|
+
else
|
38
|
+
raise(APIError, "Something went wrong when calling wpvulndb - got a #{response.code} code: '#{response.body[0..50]}'")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class DataMapper
|
44
|
+
def call(response_data, plugin_slug)
|
45
|
+
plugin_data = response_data.fetch(plugin_slug) do
|
46
|
+
raise(UnexpectedJSONError, "Couldn't find data for '#{plugin_slug}' in api response")
|
47
|
+
end
|
48
|
+
|
49
|
+
vulns = plugin_data.fetch('vulnerabilities') do
|
50
|
+
raise(UnexpectedJSONError, "Couldn't find a list of vulnerabilities")
|
51
|
+
end
|
52
|
+
|
53
|
+
vulns.map{ |v| Advisory.new(v) }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class Advisory
|
58
|
+
attr_reader :title
|
59
|
+
attr_reader :fixed_in
|
60
|
+
|
61
|
+
def initialize(data)
|
62
|
+
@id = data.fetch('id')
|
63
|
+
@title = data.fetch('title')
|
64
|
+
@created_at = data.fetch('created_at')
|
65
|
+
@fixed_in = data.fetch('fixed_in')
|
66
|
+
end
|
67
|
+
|
68
|
+
def date
|
69
|
+
Date.parse(@created_at)
|
70
|
+
end
|
71
|
+
|
72
|
+
def url
|
73
|
+
"https://wpvulndb.com/vulnerabilities/#{@id}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/pluginscan.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
|
2
|
+
require 'pluginscan/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |spec|
|
5
|
+
spec.name = "pluginscan"
|
6
|
+
spec.version = Pluginscan::VERSION
|
7
|
+
spec.authors = ["dxw"]
|
8
|
+
spec.email = ["security@dxw.com"]
|
9
|
+
spec.homepage = "https://twinkie.dxw.net/dxw/pluginscan"
|
10
|
+
spec.description = %q(Scans WordPress plugins for potential issues and vulnerabilities)
|
11
|
+
spec.summary = %q(Does stuff)
|
12
|
+
|
13
|
+
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
14
|
+
spec.executables = ['pluginscan']
|
15
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
16
|
+
spec.require_paths = ["lib"]
|
17
|
+
|
18
|
+
spec.add_dependency "rainbow", "~> 2.0"
|
19
|
+
spec.add_dependency "httparty", "< 1"
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
22
|
+
spec.add_development_dependency "rspec", "~> 3.4.0", '>= 3.4.0'
|
23
|
+
spec.add_development_dependency "webmock", "< 3"
|
24
|
+
spec.add_development_dependency "vcr", "< 4"
|
25
|
+
spec.add_development_dependency "simplecov", "< 1"
|
26
|
+
spec.add_development_dependency "rubocop", "< 1"
|
27
|
+
spec.add_development_dependency "fuubar", "~> 2"
|
28
|
+
spec.add_development_dependency "pry", "~> 0"
|
29
|
+
spec.add_development_dependency "rake", ">= 10.0.0"
|
30
|
+
spec.add_development_dependency "geminabox-release", "~> 0.2", ">= 0.2.0"
|
31
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'acceptance_spec_helper'
|
2
|
+
require 'support/heredoc_helper'
|
3
|
+
|
4
|
+
RSpec.describe Pluginscan::Scanner do
|
5
|
+
before do
|
6
|
+
# these are slow, so don't run them if we don't have to
|
7
|
+
stub_cloc
|
8
|
+
stub_sloccount
|
9
|
+
stub_vuln_check
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '.scan' do
|
13
|
+
let(:output) { StringIO.new }
|
14
|
+
|
15
|
+
describe "CLOC Report", type: [:file, :process] do
|
16
|
+
subject(:scanner) { Pluginscan::Scanner.new(sloccount: false, output: output) }
|
17
|
+
before(:all) { setup_tempdir 'tmp' }
|
18
|
+
|
19
|
+
it "reports on sloc by language" do
|
20
|
+
LanguageCount = Struct.new :language, :sloc, :file_count
|
21
|
+
|
22
|
+
cloc_output = <<-EOS.heredoc_unindent
|
23
|
+
files,language,blank,comment,code,"github.com/AlDanial/cloc v 1.70 T=0.29 s (284.9 files/s, 65539.8 lines/s)"
|
24
|
+
20,PHP,4584,0,6628
|
25
|
+
EOS
|
26
|
+
|
27
|
+
stub_cloc(result: cloc_output)
|
28
|
+
|
29
|
+
php = coloured_cyan('PHP')
|
30
|
+
count = coloured_red('6628')
|
31
|
+
scanner.scan 'tmp'
|
32
|
+
expect(output.string).to match(/#{php}\s*#{count} lines across 20 files/)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "displays a message when there was no code" do
|
36
|
+
stub_cloc(result: "")
|
37
|
+
scanner.scan 'tmp'
|
38
|
+
expect(output.string).to match(/CLOC didn't find any code/)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "displays a message when cloc was unavailable (doesn't error out)" do
|
42
|
+
stub_cloc(which_result: which_failure)
|
43
|
+
scanner.scan 'tmp'
|
44
|
+
expect(output.string).to match(/The 'cloc' command is unavailable/)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "displays a message when cloc threw an error (doesn't error out)" do
|
48
|
+
stub_cloc(result: "Some nonsense", process_status: failed_process_status)
|
49
|
+
scanner.scan 'tmp'
|
50
|
+
expect(output.string).to match(/Some nonsense/)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|