pluginscan 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.gitlab-ci.yml +16 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +46 -0
  6. data/.rubocop_todo.yml +36 -0
  7. data/CHANGELOG.md +89 -0
  8. data/Gemfile +4 -0
  9. data/Gemfile.lock +90 -0
  10. data/README.md +56 -0
  11. data/Rakefile +2 -0
  12. data/TODO.md +8 -0
  13. data/bin/pluginscan +53 -0
  14. data/lib/file_creator.rb +18 -0
  15. data/lib/pluginscan.rb +69 -0
  16. data/lib/pluginscan/error.rb +9 -0
  17. data/lib/pluginscan/error_printer.rb +17 -0
  18. data/lib/pluginscan/file_finder.rb +42 -0
  19. data/lib/pluginscan/printer.rb +14 -0
  20. data/lib/pluginscan/reports/cloc_report.rb +27 -0
  21. data/lib/pluginscan/reports/cloc_report/cloc.rb +21 -0
  22. data/lib/pluginscan/reports/cloc_report/cloc_printer.rb +42 -0
  23. data/lib/pluginscan/reports/cloc_report/cloc_scanner.rb +41 -0
  24. data/lib/pluginscan/reports/cloc_report/system_cloc.rb +33 -0
  25. data/lib/pluginscan/reports/issues_report.rb +24 -0
  26. data/lib/pluginscan/reports/issues_report/error_list_printer.rb +99 -0
  27. data/lib/pluginscan/reports/issues_report/issue_checks.rb +382 -0
  28. data/lib/pluginscan/reports/issues_report/issue_checks/check.rb +55 -0
  29. data/lib/pluginscan/reports/issues_report/issue_checks/comment_checker.rb +13 -0
  30. data/lib/pluginscan/reports/issues_report/issue_checks/function_check.rb +32 -0
  31. data/lib/pluginscan/reports/issues_report/issue_checks/variable_check.rb +14 -0
  32. data/lib/pluginscan/reports/issues_report/issue_checks/variable_safety_checker.rb +112 -0
  33. data/lib/pluginscan/reports/issues_report/issues_models/check_findings.rb +29 -0
  34. data/lib/pluginscan/reports/issues_report/issues_models/issues.rb +31 -0
  35. data/lib/pluginscan/reports/issues_report/issues_printer.rb +34 -0
  36. data/lib/pluginscan/reports/issues_report/issues_printer/check_findings_printer.rb +37 -0
  37. data/lib/pluginscan/reports/issues_report/issues_printer/file_issues_printer.rb +36 -0
  38. data/lib/pluginscan/reports/issues_report/issues_printer/finding_printer.rb +38 -0
  39. data/lib/pluginscan/reports/issues_report/issues_printer_factory.rb +19 -0
  40. data/lib/pluginscan/reports/issues_report/issues_scanner.rb +49 -0
  41. data/lib/pluginscan/reports/issues_report/issues_scanner/file_issues_scanner.rb +39 -0
  42. data/lib/pluginscan/reports/issues_report/issues_scanner/line_issues_scanner.rb +15 -0
  43. data/lib/pluginscan/reports/issues_report/issues_scanner/utf8_checker.rb +14 -0
  44. data/lib/pluginscan/reports/sloccount_report.rb +26 -0
  45. data/lib/pluginscan/reports/sloccount_report/sloccount.rb +19 -0
  46. data/lib/pluginscan/reports/sloccount_report/sloccount_printer.rb +22 -0
  47. data/lib/pluginscan/reports/sloccount_report/sloccount_scanner.rb +86 -0
  48. data/lib/pluginscan/reports/vulnerability_report.rb +28 -0
  49. data/lib/pluginscan/reports/vulnerability_report/advisories_api.rb +23 -0
  50. data/lib/pluginscan/reports/vulnerability_report/vulnerabilities_printer.rb +55 -0
  51. data/lib/pluginscan/reports/vulnerability_report/vulnerability_scanner.rb +17 -0
  52. data/lib/pluginscan/reports/vulnerability_report/wp_vuln_db_api.rb +77 -0
  53. data/lib/pluginscan/version.rb +3 -0
  54. data/pluginscan.gemspec +31 -0
  55. data/spec/acceptance/cloc_spec.rb +54 -0
  56. data/spec/acceptance/create_error_list_file_spec.rb +29 -0
  57. data/spec/acceptance/issues_spec.rb +197 -0
  58. data/spec/acceptance/pluginscan_spec.rb +18 -0
  59. data/spec/acceptance/sloccount_spec.rb +39 -0
  60. data/spec/acceptance/vulnerabilities_spec.rb +57 -0
  61. data/spec/acceptance_spec_helper.rb +10 -0
  62. data/spec/checks_examples_spec.rb +352 -0
  63. data/spec/file_creator_spec.rb +51 -0
  64. data/spec/pluginscan/cloc_scanner/cloc_scanner_spec.rb +64 -0
  65. data/spec/pluginscan/cloc_scanner/cloc_spec.rb +30 -0
  66. data/spec/pluginscan/file_finder_spec.rb +91 -0
  67. data/spec/pluginscan/issues_scanner/check_findings_spec.rb +22 -0
  68. data/spec/pluginscan/issues_scanner/error_list_printer_ignores_spec.rb +35 -0
  69. data/spec/pluginscan/issues_scanner/error_list_printer_spec.rb +42 -0
  70. data/spec/pluginscan/issues_scanner/file_issues_scanner_spec.rb +25 -0
  71. data/spec/pluginscan/issues_scanner/issues_printer_factory_spec.rb +9 -0
  72. data/spec/pluginscan/issues_scanner/issues_spec.rb +55 -0
  73. data/spec/pluginscan/issues_scanner/variable_check_spec.rb +13 -0
  74. data/spec/pluginscan/issues_scanner/variable_safety_checker_spec.rb +81 -0
  75. data/spec/pluginscan/issues_scanner_spec.rb +21 -0
  76. data/spec/pluginscan/sloccount_scanner/sloccount_scanner_spec.rb +95 -0
  77. data/spec/pluginscan/sloccount_scanner/sloccount_spec.rb +72 -0
  78. data/spec/pluginscan/vulnerability_scanner_spec.rb +96 -0
  79. data/spec/process_spec_helper.rb +6 -0
  80. data/spec/spec_helper.rb +70 -0
  81. data/spec/support/acceptance_helpers.rb +68 -0
  82. data/spec/support/file_helpers.rb +35 -0
  83. data/spec/support/heredoc_helper.rb +7 -0
  84. data/spec/support/process_helpers.rb +25 -0
  85. data/spec/support/shared_examples_for_issue_checks.rb +31 -0
  86. data/spec/support/vcr_helper.rb +6 -0
  87. data/vcr_cassettes/wpvulndb/relevanssi.yml +78 -0
  88. 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
@@ -0,0 +1,3 @@
1
+ module Pluginscan
2
+ VERSION = "0.9.0".freeze
3
+ end
@@ -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