dependency_spy 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,102 @@
1
+ # dependency_spy - Finds known vulnerabilities in your dependencies
2
+ # Copyright (C) 2017-2018 Rodrigo Fernandes
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Affero General Public License as
6
+ # published by the Free Software Foundation, either version 3 of the
7
+ # License, or (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU Affero General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Affero General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ require 'net/http'
18
+ require 'socket'
19
+ require 'yaml'
20
+ require 'bibliothecary'
21
+ require 'semantic_range'
22
+ require 'yavdb'
23
+ require 'yavdb/constants'
24
+
25
+ require_relative 'dependency_spy/dtos/dependency'
26
+ require_relative 'dependency_spy/semver'
27
+
28
+ module DependencySpy
29
+ class API
30
+
31
+ def self.check(path = Dir.pwd, platform = nil, database_path = YAVDB::Constants::DEFAULT_YAVDB_DATABASE_PATH)
32
+ unless File.exist?(database_path)
33
+ puts 'Could not find local vulnerability database, going to download the database.'
34
+ YAVDB::API.download_database(false, YAVDB::Constants::DEFAULT_YAVDB_PATH)
35
+ end
36
+
37
+ path = File.expand_path(path)
38
+ package_managers = find_platform(platform)
39
+ file_list = if File.file?(path)
40
+ path = File.dirname(path)
41
+ [File.basename(path)]
42
+ else
43
+ cmd = `find #{path} -type f | grep -vE "#{Bibliothecary.ignored_files_regex}"`
44
+ cmd.split("\n").sort
45
+ end
46
+ manifests = package_managers.map { |pm| pm.analyse(path, file_list) }.flatten.compact
47
+ manifests.map do |manifest|
48
+ package_manager = manifest[:platform]
49
+ manifest_filename = manifest[:path]
50
+ manifest_kind = manifest[:kind]
51
+
52
+ dependency_vulns = manifest[:dependencies].map do |dependency|
53
+ package_name = dependency[:name] || dependency['name']
54
+ version = dependency[:requirement] || dependency['version']
55
+ type = dependency[:type] || dependency['type']
56
+
57
+ package_vulns = vulns(manifest[:platform], package_name, database_path)
58
+
59
+ vulnerabilities = package_vulns.select do |vuln|
60
+ vulnerable = vuln.vulnerable_versions ? vuln.vulnerable_versions.any? { |vv| DependencySpy::SemVer.intersects(vv, version) } : false
61
+ unaffected = vuln.unaffected_versions ? vuln.unaffected_versions.any? { |vu| DependencySpy::SemVer.intersects(vu, version) } : false
62
+ patched = vuln.patched_versions ? vuln.patched_versions.any? { |vp| DependencySpy::SemVer.intersects(vp, version) } : false
63
+
64
+ vulnerable ||
65
+ (vuln.unaffected_versions&.any? && !unaffected) ||
66
+ (vuln.patched_versions&.any? && !patched)
67
+ end
68
+
69
+ Dependency.new(package_name, version, type, vulnerabilities.uniq)
70
+ end
71
+
72
+ Manifest.new(package_manager, manifest_filename, manifest_kind, dependency_vulns.uniq)
73
+ end
74
+ end
75
+
76
+ def self.update(vuln_repo_path = YAVDB::Constants::DEFAULT_YAVDB_PATH)
77
+ YAVDB::API.download_database(true, vuln_repo_path)
78
+ end
79
+
80
+ class << self
81
+
82
+ private
83
+
84
+ def vulns(package_manager, package_name, vuln_database_path)
85
+ YAVDB::API.list_vulnerabilities(package_manager, package_name, vuln_database_path)
86
+ end
87
+
88
+ def find_platform(platform)
89
+ if platform.nil?
90
+ Bibliothecary.package_managers
91
+ else
92
+ Bibliothecary::Parsers.constants
93
+ .select { |c| c.to_s.downcase.include?(platform) }
94
+ .map { |c| Bibliothecary::Parsers.const_get(c) }
95
+ .sort_by { |c| c.to_s.downcase }
96
+ end
97
+ end
98
+
99
+ end
100
+
101
+ end
102
+ end
@@ -0,0 +1,71 @@
1
+ # dependency_spy - Finds known vulnerabilities in your dependencies
2
+ # Copyright (C) 2017-2018 Rodrigo Fernandes
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Affero General Public License as
6
+ # published by the Free Software Foundation, either version 3 of the
7
+ # License, or (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU Affero General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Affero General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ require 'thor'
18
+ require 'yavdb/constants'
19
+
20
+ require_relative '../dependency_spy'
21
+ require_relative 'formatters/text'
22
+ require_relative 'formatters/json'
23
+ require_relative 'formatters/yaml'
24
+ require_relative 'outputs/stdout'
25
+ require_relative 'outputs/file'
26
+
27
+ module DependencySpy
28
+ class CLI < Thor
29
+
30
+ default_task :check
31
+ map '--version' => :version
32
+
33
+ FORMATTERS = [
34
+ DependencySpy::Formatters::Text,
35
+ DependencySpy::Formatters::Json,
36
+ DependencySpy::Formatters::Yaml
37
+ ]
38
+
39
+ class_option('verbose', :type => :boolean, :default => false)
40
+
41
+ desc('check', 'Check dependencies for known vulnerabilities')
42
+ method_option('path', :aliases => :p, :type => :string, :default => Dir.pwd)
43
+ method_option('formatter', :aliases => :f, :type => :string, :enum => FORMATTERS.map { |f| f.name.split('::').last.downcase }, :default => FORMATTERS.first.name.split('::').last.downcase)
44
+ method_option('platform', :aliases => :m, :type => :string, :enum => YAVDB::Constants::POSSIBLE_PACKAGE_MANAGERS.map(&:downcase))
45
+ method_option('output-path', :aliases => :o, :type => :string)
46
+ method_option('database-path', :type => :string, :aliases => :p, :default => YAVDB::Constants::DEFAULT_YAVDB_DATABASE_PATH)
47
+
48
+ def check
49
+ manifests = API.check(options['path'], options['platform'], options['database-path'])
50
+
51
+ formatted_output =
52
+ FORMATTERS
53
+ .find { |f| f.name.split('::').last.downcase == options['formatter'] }
54
+ .format(manifests)
55
+
56
+ if options['output-path']
57
+ DependencySpy::Outputs::FileSystem.write(options['output-path'], formatted_output)
58
+ else
59
+ DependencySpy::Outputs::StdOut.write(formatted_output)
60
+ end
61
+ end
62
+
63
+ method_option('vuln-db-path', :aliases => :d, :type => :string, :default => YAVDB::Constants::DEFAULT_YAVDB_PATH)
64
+ desc('update', 'Download or update database from the official yavdb repository.')
65
+
66
+ def update
67
+ API.update(options['vuln-db-path'])
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,77 @@
1
+ # dependency_spy - Finds known vulnerabilities in your dependencies
2
+ # Copyright (C) 2017-2018 Rodrigo Fernandes
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Affero General Public License as
6
+ # published by the Free Software Foundation, either version 3 of the
7
+ # License, or (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU Affero General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Affero General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ module DependencySpy
18
+
19
+ class Manifest < Struct.new(
20
+ :platform, # [String]
21
+ :path, # [String]
22
+ :kind, # [String]
23
+ :dependencies # Array[Dependency]
24
+ )
25
+
26
+ def to_map
27
+ map = {}
28
+ members.each do |m|
29
+ next unless self[m] && (
30
+ (self[m].is_a?(String) && !self[m].empty?) ||
31
+ (self[m].is_a?(Array) && self[m].any?))
32
+
33
+ map[m.to_s] = self[m] if self[m]
34
+ end
35
+ map
36
+ end
37
+
38
+ def to_json(*attrs)
39
+ to_map.to_json(*attrs)
40
+ end
41
+
42
+ def to_yaml(*attrs)
43
+ to_map.to_yaml(*attrs)
44
+ end
45
+
46
+ end
47
+
48
+ class Dependency < Struct.new(
49
+ :name, # [String]
50
+ :version, # [String]
51
+ :type, # [String]
52
+ :vulnerabilities # Array[Advisory]
53
+ )
54
+
55
+ def to_map
56
+ map = {}
57
+ members.each do |m|
58
+ next unless self[m] && (
59
+ (self[m].is_a?(String) && !self[m].empty?) ||
60
+ (self[m].is_a?(Array) && self[m].any?))
61
+
62
+ map[m.to_s] = self[m] if self[m]
63
+ end
64
+ map
65
+ end
66
+
67
+ def to_json(*args)
68
+ to_map.to_json(*args)
69
+ end
70
+
71
+ def to_yaml(*args)
72
+ to_map.to_yaml(*args)
73
+ end
74
+
75
+ end
76
+
77
+ end
@@ -0,0 +1,40 @@
1
+ # dependency_spy - Finds known vulnerabilities in your dependencies
2
+ # Copyright (C) 2017-2018 Rodrigo Fernandes
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Affero General Public License as
6
+ # published by the Free Software Foundation, either version 3 of the
7
+ # License, or (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU Affero General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Affero General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ module DependencySpy
18
+ class Formatters
19
+ class Json
20
+
21
+ def self.format(manifests)
22
+ filtered_manifests = manifests.map do |manifest|
23
+ manifest[:dependencies] = manifest[:dependencies].map do |dependency|
24
+ next unless dependency[:vulnerabilities].any?
25
+
26
+ dependency[:vulnerabilities] = dependency[:vulnerabilities].map(&:to_map)
27
+ dependency
28
+ end.reject(&:nil?).map(&:to_map)
29
+ manifest
30
+ end
31
+
32
+ filtered_manifests
33
+ .reject { |m| m[:dependencies].nil? }
34
+ .map(&:to_map)
35
+ .map(&:to_json)
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,53 @@
1
+ # dependency_spy - Finds known vulnerabilities in your dependencies
2
+ # Copyright (C) 2017-2018 Rodrigo Fernandes
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Affero General Public License as
6
+ # published by the Free Software Foundation, either version 3 of the
7
+ # License, or (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU Affero General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Affero General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ module DependencySpy
18
+ class Formatters
19
+ class Text
20
+
21
+ def self.format(manifests)
22
+ manifests_text = manifests.map do |manifest|
23
+ manifest_header = "#{manifest.platform}: #{manifest.kind} ~> #{manifest.path} "
24
+ manifest_body = manifest.dependencies.map do |package|
25
+ next unless package.vulnerabilities.any?
26
+
27
+ package_header = " Vulnerable: #{package.name}/#{package.type}:#{package.version}"
28
+ package_body = package.vulnerabilities.map do |vuln|
29
+ first = " Title: #{vuln.title}\n"
30
+ second = " Severity: #{(vuln.severity || 'unknown').capitalize}\n"
31
+ third = " Source: #{vuln.source_url}\n\n"
32
+
33
+ "#{first}#{second}#{third}"
34
+ end
35
+
36
+ "#{package_header}\n#{package_body.join("\n")}"
37
+ end
38
+
39
+ next unless manifest_body.any?
40
+
41
+ "#{manifest_header}\n#{manifest_body.reject(&:nil?).join("\n")}"
42
+ end
43
+
44
+ if manifests_text.any?
45
+ manifests_text.join("\n")
46
+ else
47
+ 'No known vulnerabilities were found in your dependencies.'
48
+ end
49
+ end
50
+
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,40 @@
1
+ # dependency_spy - Finds known vulnerabilities in your dependencies
2
+ # Copyright (C) 2017-2018 Rodrigo Fernandes
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Affero General Public License as
6
+ # published by the Free Software Foundation, either version 3 of the
7
+ # License, or (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU Affero General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Affero General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ module DependencySpy
18
+ class Formatters
19
+ class Yaml
20
+
21
+ def self.format(manifests)
22
+ filtered_manifests = manifests.map do |manifest|
23
+ manifest[:dependencies] = manifest[:dependencies].map do |dependency|
24
+ next unless dependency[:vulnerabilities].any?
25
+
26
+ dependency[:vulnerabilities] = dependency[:vulnerabilities].map(&:to_map)
27
+ dependency
28
+ end.reject(&:nil?).map(&:to_map)
29
+ manifest
30
+ end
31
+
32
+ filtered_manifests
33
+ .reject { |m| m[:dependencies].nil? }
34
+ .map(&:to_map)
35
+ .map(&:to_yaml)
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,33 @@
1
+ # dependency_spy - Finds known vulnerabilities in your dependencies
2
+ # Copyright (C) 2017-2018 Rodrigo Fernandes
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Affero General Public License as
6
+ # published by the Free Software Foundation, either version 3 of the
7
+ # License, or (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU Affero General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Affero General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ module DependencySpy
18
+ class Outputs
19
+ class FileSystem
20
+
21
+ def self.write(path, output)
22
+ file = File.expand_path(path)
23
+ path_directory = File.dirname(file)
24
+ FileUtils.mkdir_p(path_directory) unless File.exist?(path_directory)
25
+
26
+ File.open(file, 'wb') do |f|
27
+ f.puts(output)
28
+ end
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,27 @@
1
+ # dependency_spy - Finds known vulnerabilities in your dependencies
2
+ # Copyright (C) 2017-2018 Rodrigo Fernandes
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Affero General Public License as
6
+ # published by the Free Software Foundation, either version 3 of the
7
+ # License, or (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU Affero General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Affero General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ module DependencySpy
18
+ class Outputs
19
+ class StdOut
20
+
21
+ def self.write(output)
22
+ puts output
23
+ end
24
+
25
+ end
26
+ end
27
+ end