dependency_spy 0.1.3

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,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