nessana 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 841b59ccd2b8e575f45f778d97a1b5c7b158c13fca2bf3bf554792ef25d931b7
4
+ data.tar.gz: 056feefeef224de161623a01593d0620def012e44346ab9d5cdae79174d41e29
5
+ SHA512:
6
+ metadata.gz: 9b1d6e33a8fccc0a1b6685c5ee6f4d98ef24a1084bc7066138a851c700b886c422c79858cb80dc33eb45ae779ee50a2a09eb234e0eb683fd7ff78105addbaaed
7
+ data.tar.gz: 2012a1a887a6b291c1b82636504104808335f6901beb7deca7ed3acf8dc38b220a39bc251df8b49ba6ec8e5d50eb693dada3e4e99343c02ff5885b9544f9c2e7
@@ -0,0 +1,13 @@
1
+ # nessana
2
+
3
+ Parse Nessus dumps and intelligently create Asana tasks.
4
+
5
+ ## Usage
6
+
7
+ ```console
8
+ $ nessana -c config.yml scan.csv
9
+ ```
10
+
11
+ ## Contributing
12
+
13
+ See [CONTRIBUTING.md](CONTRIBUTING.md)
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'nessana/executor'
4
+
5
+ Nessana::Executor.execute!
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'asana'
4
+ require 'csv'
5
+ require 'json'
6
+ require 'logger'
7
+ require 'pp'
8
+ require 'pry'
9
+ require 'ruby-prof'
10
+ require 'ruby-prof-flamegraph'
11
+ require 'yaml'
12
+
13
+ require 'nessana/vulnerability'
14
+ require 'nessana/vulnerability_list'
15
+
16
+ result = RubyProf.profile do
17
+
18
+ $logger = Logger.new(STDOUT)
19
+ $logger.level = Logger::DEBUG
20
+
21
+ $file_contents = open('secrets.yml', 'rb') do |io|
22
+ io.read
23
+ end
24
+
25
+ $asana_access_token = YAML.load($file_contents)['ASANA_PAT']
26
+
27
+ $client = Asana::Client.new do |c|
28
+ c.authentication :access_token, $asana_access_token
29
+ end
30
+
31
+ workspace = $client.workspaces.find_all.select do |workspace|
32
+ workspace.name == 'stolaf.org'
33
+ end.first
34
+
35
+ project = $client.projects.find_all(workspace: workspace.id).select do |project|
36
+ project.name == '[Sys] Security'
37
+ end.first
38
+
39
+ tag = $client.tags.find_all(workspace: workspace.id).select do |tag|
40
+ tag.name == 'Automated [Nessana]'
41
+ end.first
42
+
43
+ pp project.sections
44
+
45
+ tasks = $client.tasks.find_by_tag(tag: tag.id).map do |flat_task|
46
+ $logger.debug "Fetching task with id=#{flat_task.id}"
47
+ $client.tasks.find_by_id(flat_task.id).to_h
48
+ end.select do |task_hash|
49
+ !task_hash["completed"]
50
+ end
51
+
52
+ pp tasks
53
+
54
+ end
55
+
56
+ printer = RubyProf::FlameGraphPrinter.new(result)
57
+ printer.print(STDERR, {})
58
+
59
+ __END__
60
+
61
+ output = nil
62
+
63
+ result = RubyProf.profile do
64
+ vulnerabilities = VulnerabilityList.from_csv(ARGV[0])
65
+
66
+ $vulnerabilities = vulnerabilities.filter_risks.filter_not_accessible
67
+
68
+ # vuln_plugin_mapping = $vulnerabilities.each_with_object({}) do |vuln, hash|
69
+ # cve_string = vuln.cve ? " (#{vuln.cve})" : ""
70
+ # puts "Adding entry for #{vuln.plugin_id}#{cve_string} on host #{vuln.host}:#{vuln.port} (#{vuln.protocol})"
71
+ # hash[vuln.plugin_id] ||= []
72
+ # hash[vuln.plugin_id].push vuln
73
+ # end
74
+
75
+ vulns_by_plugin = $vulnerabilities.each_with_object({}) do |vuln, hash|
76
+ hash[vuln.plugin_id] ||= []
77
+ hash[vuln.plugin_id] << vuln
78
+ end
79
+
80
+ reports = vulns_by_plugin.map do |plugin_id, vulns|
81
+ uniqued_titles = vulns.map do |vuln|
82
+ vuln.name
83
+ end.uniq
84
+
85
+ uniqued_cves = vulns.map do |vuln|
86
+ vuln.cve
87
+ end.uniq
88
+
89
+ uniqued_cvsss = vulns.map do |vuln|
90
+ vuln.cvss
91
+ end.uniq
92
+
93
+ uniqued_risks = vulns.map do |vuln|
94
+ vuln.risk
95
+ end.uniq
96
+
97
+ throw "Plugin #{plugin_id} produced #{uniqued_titles.count} != 1 unique titles!" unless uniqued_titles.count == 1
98
+ throw "Plugin #{plugin_id} produced #{uniqued_cvsss.count} != 1 unique CVSS's!" unless uniqued_cvsss.count == 1
99
+ throw "Plugin #{plugin_id} produced #{uniqued_risks.count} != 1 unique risks!" unless uniqued_risks.count == 1
100
+
101
+ uniqued_hosts = vulns.map do |vuln|
102
+ vuln.readable_host
103
+ end.uniq
104
+
105
+ uniqued_synopses = vulns.map do |vuln|
106
+ vuln.synopsis
107
+ end.uniq
108
+
109
+ throw "More than one unique synopsis given?" unless uniqued_synopses.count == 1
110
+
111
+ uniqued_descriptions = vulns.map do |vuln|
112
+ vuln.description
113
+ end.uniq
114
+
115
+ throw "More than one unique description given?" unless uniqued_descriptions.count == 1
116
+
117
+ uniqued_solutions = vulns.map do |vuln|
118
+ vuln.solution
119
+ end.uniq
120
+
121
+ throw "More than one unique solution given?" unless uniqued_solutions.count == 1
122
+
123
+ {
124
+ cvss: uniqued_cvsss.first,
125
+ title: "[Nessus #{plugin_id}] #{uniqued_titles.join(', ')}",
126
+ body: "CVE: #{uniqued_cves.first || 'N/A'}\nCVSS: #{uniqued_cvsss.first || 'N/A'}\nRisk: #{uniqued_risks.first || 'N/A'}\n\nSYNOPSIS\n\n#{uniqued_synopses.first}\n\nDESCRIPTION\n\n#{uniqued_descriptions.first.join("\n\n")}\n\nSOLUTION\n\n#{uniqued_solutions.first.join("\n\n")}\n\nThis issue was detected on #{uniqued_hosts.count} hosts: #{uniqued_hosts.join(', ')}",
127
+ hosts: uniqued_hosts
128
+ }
129
+ end
130
+
131
+ output = reports.sort do |report_a, report_b|
132
+ report_b[:cvss] <=> report_a[:cvss]
133
+ end.map do |report|
134
+ [report[:title], report[:body]]
135
+ end.to_a
136
+ end
137
+
138
+ printer = RubyProf::GraphPrinter.new(result)
139
+ printer.print(STDOUT, {})
140
+
141
+ CSV.open(ARGV[1], 'wb') do |csv|
142
+ output.each do |row|
143
+ csv << row
144
+ end
145
+ end
@@ -0,0 +1,6 @@
1
+ require 'nessana/detection'
2
+ require 'nessana/dump'
3
+ require 'nessana/executor'
4
+ require 'nessana/filter'
5
+ require 'nessana/version'
6
+ require 'nessana/vulnerability'
@@ -0,0 +1,45 @@
1
+ module Nessana
2
+ class Detection
3
+ attr_reader :host, :protocol, :port
4
+ attr_accessor :status
5
+
6
+ def initialize(host, protocol, port, status = nil)
7
+ @host = host
8
+ @protocol = protocol
9
+ @port = port
10
+ @status = status
11
+ end
12
+
13
+ def to_s
14
+ "#{@status ? status_prefix : ''}#{@host}:#{@port}/#{@protocol}"
15
+ end
16
+
17
+ def hash
18
+ "#{@host}:#{@port}/#{@protocol}".hash
19
+ end
20
+
21
+ def ==(other)
22
+ @host == other.host &&
23
+ @protocol == other.protocol &&
24
+ @port == other.port &&
25
+ @status == other.status || false
26
+ end
27
+
28
+ alias eql? ==
29
+
30
+ protected
31
+
32
+ def status_prefix
33
+ case @status
34
+ when :added
35
+ '+ '
36
+ when :removed
37
+ '- '
38
+ when :present
39
+ ' '
40
+ else
41
+ '? '
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,176 @@
1
+ require 'time'
2
+
3
+ require 'csv'
4
+ require 'fastcsv'
5
+ require 'tty-spinner'
6
+
7
+ require 'nessana/detection'
8
+ require 'nessana/vulnerability'
9
+
10
+ module Nessana
11
+ class Dump < Hash
12
+ attr_reader :filters
13
+ attr_reader :filename
14
+
15
+ def initialize(filename = nil, filters = [])
16
+ @filename, @filters = filename, filters
17
+
18
+ if @filename
19
+ if File.readable?(@filename)
20
+ spinner_options = {
21
+ success_mark: "\u2713".encode('utf-8'),
22
+ format: :dots_3
23
+ }
24
+ spinner = TTY::Spinner.new("[:spinner] Loading #{@filename}...", **spinner_options)
25
+ spinner.auto_spin
26
+
27
+ read_csv!
28
+
29
+ spinner.success('done!')
30
+ else
31
+ throw 'file not readable; sad face'
32
+ end
33
+ end
34
+ end
35
+
36
+ def -(other)
37
+ spinner_options = {
38
+ success_mark: "\u2713".encode('utf-8'),
39
+ format: :dots_3
40
+ }
41
+
42
+ spinner = TTY::Spinner.new('[:spinner] :action...', **spinner_options)
43
+ spinner.update(action: 'Generating detections...')
44
+
45
+ other_plugin_ids = other.keys
46
+ self_plugin_ids = keys
47
+
48
+ spinner.update(action: 'Finding L detections')
49
+
50
+ other_detection_pairs = other.map do |plugin_id, vulnerability|
51
+ spinner.update(action: "Finding L detections (#{plugin_id})")
52
+ spinner.auto_spin
53
+
54
+ vulnerability.detections.map do |detection|
55
+ { plugin_id => detection }
56
+ end
57
+ end
58
+
59
+ other_detections = Set.new(other_detection_pairs.flatten)
60
+
61
+ spinner.update(action: 'Finding R detections')
62
+
63
+ detection_pairs = map do |plugin_id, vulnerability|
64
+ spinner.update(action: "Finding R detections (#{plugin_id})")
65
+ spinner.auto_spin
66
+
67
+ vulnerability.detections.map do |detection|
68
+ { plugin_id => detection }
69
+ end
70
+ end
71
+
72
+ self_detections = Set.new(detection_pairs.flatten)
73
+
74
+ spinner.update(action: 'Joining detection sets')
75
+ spinner.auto_spin
76
+
77
+ detections = Set.new([other_detections, self_detections]).flatten
78
+
79
+ spinner.update(action: 'Processing detections')
80
+ spinner.auto_spin
81
+
82
+ detections.each do |detection_entry|
83
+ in_self = self_detections.include? detection_entry
84
+ in_other = other_detections.include? detection_entry
85
+
86
+ detection = detection_entry.values.first
87
+
88
+ if in_self && in_other
89
+ detection.status = :present
90
+ elsif !in_self && in_other
91
+ detection.status = :removed
92
+ elsif in_self && !in_other
93
+ detection.status = :added
94
+ else
95
+ detection.status = true
96
+ end
97
+ end
98
+
99
+ spinner.success('done!')
100
+
101
+ added_plugin_ids = self_plugin_ids - other_plugin_ids
102
+ deleted_plugin_ids = other_plugin_ids - self_plugin_ids
103
+ all_plugin_ids = other_plugin_ids + added_plugin_ids
104
+
105
+ all_vulnerabilities = all_plugin_ids.map do |plugin_id|
106
+ vulnerability = nil
107
+
108
+ if !self[plugin_id]
109
+ vulnerability = other[plugin_id].clone
110
+ else
111
+ vulnerability = self[plugin_id].clone
112
+ end
113
+
114
+ plugin_detections = detections.select do |detection_entry|
115
+ detection_entry.keys.first == vulnerability.plugin_id
116
+ end.map do |detection_entry|
117
+ detection_entry.values.first
118
+ end
119
+
120
+ vulnerability.detections = plugin_detections.map do |detection|
121
+ detection.dup
122
+ end
123
+
124
+ vulnerability
125
+ end
126
+
127
+ all_vulnerabilities
128
+ end
129
+
130
+ def read_csv!
131
+ data = read_csv(filename)
132
+
133
+ filtered_data = data.select do |_, vulnerability|
134
+ !vulnerability.matches?(@filters)
135
+ end
136
+
137
+ merge!(filtered_data)
138
+ end
139
+
140
+ protected
141
+
142
+ def read_csv(filename)
143
+ dump_data = {}
144
+
145
+ first_row = true
146
+
147
+ File.open(filename, 'rb') do |io|
148
+ io.advise(:willneed)
149
+ io.advise(:noreuse)
150
+ io.advise(:sequential)
151
+
152
+ FastCSV.raw_parse(io) do |row|
153
+ if first_row
154
+ first_row = false
155
+ next
156
+ end
157
+
158
+ row_nessus_data = row[0..3] + row[7..-1]
159
+ row_detection_data = row[4..6]
160
+
161
+ plugin_id = row[0]
162
+
163
+ unless dump_data[plugin_id]
164
+ dump_data[plugin_id] = Vulnerability.new(*row_nessus_data)
165
+ end
166
+
167
+ row_detection = Detection.new(*row_detection_data)
168
+
169
+ dump_data[plugin_id].add_detection(row_detection)
170
+ end
171
+ end
172
+
173
+ dump_data
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,100 @@
1
+ require 'optparse'
2
+ require 'pp'
3
+ require 'ruby-prof'
4
+ require 'ruby-prof-flamegraph'
5
+
6
+ require 'nessana/executor/execution_configuration'
7
+ require 'nessana/filter'
8
+ require 'nessana/dump'
9
+
10
+ module Nessana
11
+ module Executor
12
+ def self.print_usage!
13
+ puts @parser
14
+ end
15
+
16
+ def self.execute!(argv = ARGV)
17
+ parse!(*argv)
18
+
19
+ RubyProf.start if !!@configuration['performance']
20
+
21
+ unless @configuration['old_filename']
22
+ $stderr.puts 'No old dump filename given; will assume no prior knowledge.'
23
+ end
24
+
25
+ unless @configuration['new_filename']
26
+ puts 'No new dump filename given; cannot do anything.'
27
+ return
28
+ end
29
+
30
+ filters = @configuration['filters'].map do |filter_hash|
31
+ Filter.new(filter_hash)
32
+ end
33
+
34
+ old_dump = @configuration['old_filename'] ? Dump.new(@configuration['old_filename'], filters) : Dump.new
35
+ new_dump = Dump.new(@configuration['new_filename'], filters)
36
+
37
+ diff = new_dump - old_dump
38
+
39
+ diff.sort do |vulnerability_a, vulnerability_b|
40
+ vulnerability_a.plugin_id.to_i <=> vulnerability_b.plugin_id.to_i
41
+ end.sort do |vulnerability_a, vulnerability_b|
42
+ vulnerability_a.cvss.to_f <=> vulnerability_b.cvss.to_f
43
+ end.each do |v|
44
+ puts "#{v}
45
+
46
+ DISCOVERIES"
47
+ v.detections.sort_by(&:port).sort_by(&:host).each do |detection|
48
+ if detection.status && detection.status != true
49
+ puts detection.to_s
50
+ end
51
+ end
52
+ puts "\n" * 2
53
+ end
54
+
55
+ if !!@configuration['performance']
56
+ result = RubyProf.stop
57
+ printer = RubyProf::FlameGraphPrinter.new(result)
58
+ io = open(@configuration['performance'], 'wb')
59
+ printer.print(io, {})
60
+ end
61
+ end
62
+
63
+ def self.parse(*argv)
64
+ configuration = ExecutionConfiguration.new
65
+
66
+ option_parser = OptionParser.new do |parser|
67
+ configuration.add_parser_hooks(parser)
68
+
69
+ if argv.count == 0
70
+ puts parser
71
+ exit 1
72
+ end
73
+
74
+ parser.parse(*argv)
75
+ end
76
+
77
+ remaining_arguments = option_parser.order!(argv)
78
+
79
+ case remaining_arguments.count
80
+ when 2
81
+ configuration['old_filename'] = remaining_arguments[0]
82
+ configuration['new_filename'] = remaining_arguments[1]
83
+ when 1
84
+ configuration['old_filename'] = nil
85
+ configuration['new_filename'] = remaining_arguments[0]
86
+ end
87
+
88
+ configuration.read_configuration_file!
89
+
90
+ configuration
91
+ end
92
+
93
+ protected
94
+
95
+ def self.parse!(*argv)
96
+ @configuration = parse(*argv)
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,88 @@
1
+ require 'mime-types'
2
+
3
+ require 'nessana/version'
4
+
5
+ module Nessana::Executor
6
+ class ExecutionConfiguration < ::Hash
7
+ def initialize
8
+ self['verbosity'] = 'info'
9
+ self['config'] = 'config.yml'
10
+ self['dump_filename'] = nil
11
+ end
12
+
13
+ # FIXME: too many lines
14
+ def add_parser_hooks(parser)
15
+ parser.banner = "Usage: #{$PROGRAM_NAME} [options] <filename.csv>"
16
+
17
+ parser.separator ''
18
+ parser.separator 'Execution Options'
19
+
20
+ add_config_option(parser)
21
+
22
+ parser.separator ''
23
+ parser.separator 'General Options'
24
+
25
+ add_usage_option(parser)
26
+ add_verbosity_option(parser)
27
+
28
+ parser.on_tail('-h', '--help', 'Show this message') do
29
+ puts parser
30
+ exit
31
+ end
32
+
33
+ parser.on_tail('--version', 'Show version') do
34
+ puts Nessana::VERSION
35
+ exit
36
+ end
37
+ end
38
+
39
+ # TODO: deep merge?
40
+ def read_configuration_file!
41
+ merge!(read_configuration_file(self['config']))
42
+ end
43
+
44
+ protected
45
+
46
+ def add_config_option(parser)
47
+ parser.on('-c', '--config CONFIG', "Load configuration from CONFIG (default: #{self['config']})") do |config|
48
+ self['config'] = config
49
+ end
50
+ end
51
+
52
+ def add_usage_option(parser)
53
+ parser.on('-h', '--help', 'Print usage summary.') do
54
+ puts parser
55
+ exit 0
56
+ end
57
+ end
58
+
59
+ def add_verbosity_option(parser)
60
+ parser.on('-v', '--verbosity VERBOSITY', "The level of verbosity to use. (default: #{self['verbosity']})")
61
+ end
62
+
63
+ def infer_mime_type(filename)
64
+ MIME::Types.type_for(filename).first.content_type
65
+ end
66
+
67
+ # FIXME: too many lines
68
+ def read_configuration_file(filename)
69
+ raise ArgumentError, 'Must pass a valid filename' if filename.nil?
70
+
71
+ mime_type = infer_mime_type(filename)
72
+ parsed = nil
73
+
74
+ data = File.open(filename, 'rb', &:read)
75
+
76
+ case mime_type
77
+ when /ya?ml/
78
+ require 'yaml'
79
+ parsed = YAML.safe_load(data, [Regexp])
80
+ when /json/
81
+ require 'json'
82
+ parsed = JSON.parse(data)
83
+ end
84
+
85
+ parsed.to_h
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,34 @@
1
+ require 'ostruct'
2
+
3
+ require 'nessana/vulnerability'
4
+
5
+ module Nessana
6
+ class Filter < ::Hash
7
+ def initialize(hash)
8
+ super
9
+
10
+ fixed_hash = hash.map do |key, value|
11
+ [key.to_sym, value]
12
+ end.to_h
13
+
14
+ merge!(fixed_hash)
15
+ end
16
+
17
+ def applies_to?(vulnerability)
18
+ each do |key, value|
19
+ method = key.to_sym
20
+
21
+ return false unless vulnerability.respond_to?(method)
22
+
23
+ case value
24
+ when Regexp
25
+ return true if vulnerability.send(method).to_s =~ value
26
+ else
27
+ return true if vulnerability.send(method).to_s == value
28
+ end
29
+ end
30
+
31
+ false
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ module Nessana
2
+ VERSION = '0.1.0'.freeze
3
+ end
@@ -0,0 +1,77 @@
1
+ module Nessana
2
+ class Vulnerability
3
+ attr_reader :plugin_id
4
+ attr_reader :cve, :cvss, :risk
5
+ attr_reader :name, :synopsis, :description, :solution
6
+ attr_reader :see_also, :plugin_output
7
+ attr_accessor :detections
8
+
9
+ def initialize(plugin_id, cve, cvss, risk, name, synopsis, description, solution, see_also, plugin_output)
10
+ @plugin_id = plugin_id
11
+ @cve = cve
12
+ @cvss = cvss
13
+ @risk = risk
14
+ @name = name
15
+ @synopsis = reformat_multiline(synopsis)
16
+ @description = reformat_multiline(description)
17
+ @solution = solution
18
+ @see_also = see_also
19
+ @plugin_output = plugin_output
20
+ end
21
+
22
+ def add_detection(detection)
23
+ @detections ||= []
24
+ @detections.push(detection)
25
+ end
26
+
27
+ def to_s
28
+ [title_line, @synopsis, cve_s, cvss_s, description_s, solution_s, see_also_s].join("\n\n")
29
+ end
30
+
31
+ def matches?(filters = [])
32
+ filters.each do |filter|
33
+ return true if filter.applies_to?(self)
34
+ end
35
+
36
+ false
37
+ end
38
+
39
+ def title_line
40
+ "[Nessus #{@plugin_id || "???"}] #{@name || "Unknown Vulnerability"} (#{@risk || "Unknown Risk"})"
41
+ end
42
+
43
+ def short_description
44
+ "#{title_line}
45
+ #{@synopsis}"
46
+ end
47
+
48
+ protected
49
+
50
+ def cve_s
51
+ @cve ? "CVE: " + @cve : "No CVE."
52
+ end
53
+
54
+ def cvss_s
55
+ @cvss ? "CVSS: " + @cvss : "No CVSS."
56
+ end
57
+
58
+ def description_s
59
+ "DESCRIPTION:\n#{@description}"
60
+ end
61
+
62
+ def solution_s
63
+ "SOLUTION:\n#{@solution}"
64
+ end
65
+
66
+ def see_also_s
67
+ "SEE ALSO:\n#{@see_also}"
68
+ end
69
+
70
+ # TODO Replace with something not O(2 n)? [n = line count]
71
+ def reformat_multiline(multiline)
72
+ multiline.split("\n").map do |line|
73
+ line.length == 0 ? "\n\n" : line
74
+ end.join(' ').gsub(/^ /,'').gsub(/ {3,}/,' ')
75
+ end
76
+ end
77
+ end
metadata ADDED
@@ -0,0 +1,252 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nessana
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kristofer Rye <kristofer.rye@gmail.com>
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-01-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: asana
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.6.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.6.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: fastcsv
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.0.6
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.0.6
41
+ - !ruby/object:Gem::Dependency
42
+ name: mime-types
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: tty-spinner
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.8.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.8.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: codecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.1.14
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.1.14
83
+ - !ruby/object:Gem::Dependency
84
+ name: coveralls
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.8.22
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.8.22
97
+ - !ruby/object:Gem::Dependency
98
+ name: guard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '2.14'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '2.14'
111
+ - !ruby/object:Gem::Dependency
112
+ name: guard-rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '4.7'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '4.7'
125
+ - !ruby/object:Gem::Dependency
126
+ name: pry
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 0.11.3
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 0.11.3
139
+ - !ruby/object:Gem::Dependency
140
+ name: rspec
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '3.7'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '3.7'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rubocop
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '0.57'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '0.57'
167
+ - !ruby/object:Gem::Dependency
168
+ name: ruby-prof
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: 0.17.0
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: 0.17.0
181
+ - !ruby/object:Gem::Dependency
182
+ name: ruby-prof-flamegraph
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: 0.3.0
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: 0.3.0
195
+ - !ruby/object:Gem::Dependency
196
+ name: simplecov
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: 0.16.1
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: 0.16.1
209
+ description: 'A Nessus dump parser and differ which can create Asana tasks.
210
+
211
+ '
212
+ email:
213
+ executables:
214
+ - nessana
215
+ extensions: []
216
+ extra_rdoc_files: []
217
+ files:
218
+ - README.md
219
+ - bin/nessana
220
+ - bin/nessana_old
221
+ - lib/nessana.rb
222
+ - lib/nessana/detection.rb
223
+ - lib/nessana/dump.rb
224
+ - lib/nessana/executor.rb
225
+ - lib/nessana/executor/execution_configuration.rb
226
+ - lib/nessana/filter.rb
227
+ - lib/nessana/version.rb
228
+ - lib/nessana/vulnerability.rb
229
+ homepage: https://github.com/rye/nessana
230
+ licenses:
231
+ - MIT
232
+ metadata: {}
233
+ post_install_message:
234
+ rdoc_options: []
235
+ require_paths:
236
+ - lib
237
+ required_ruby_version: !ruby/object:Gem::Requirement
238
+ requirements:
239
+ - - "~>"
240
+ - !ruby/object:Gem::Version
241
+ version: '2.4'
242
+ required_rubygems_version: !ruby/object:Gem::Requirement
243
+ requirements:
244
+ - - ">="
245
+ - !ruby/object:Gem::Version
246
+ version: '0'
247
+ requirements: []
248
+ rubygems_version: 3.0.2
249
+ signing_key:
250
+ specification_version: 4
251
+ summary: A Nessus dump parser and Asana task creator
252
+ test_files: []