dependency_spy 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +41 -0
- data/.gitignore +114 -0
- data/.rspec +3 -0
- data/.rubocop.yml +333 -0
- data/.ruby-version +1 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +114 -0
- data/LICENSE +661 -0
- data/README.md +94 -0
- data/Rakefile +6 -0
- data/bin/console +7 -0
- data/bin/dependency_spy +5 -0
- data/bin/depspy +3 -0
- data/bin/setup +8 -0
- data/dependency_spy.gemspec +42 -0
- data/examples/Gemfile +6 -0
- data/examples/Gemfile.lock +80 -0
- data/examples/npm-shrinkwrap.json +3823 -0
- data/examples/package.json +85 -0
- data/examples/yarn.lock +3010 -0
- data/lib/dependency_spy.rb +102 -0
- data/lib/dependency_spy/cli.rb +71 -0
- data/lib/dependency_spy/dtos/dependency.rb +77 -0
- data/lib/dependency_spy/formatters/json.rb +40 -0
- data/lib/dependency_spy/formatters/text.rb +53 -0
- data/lib/dependency_spy/formatters/yaml.rb +40 -0
- data/lib/dependency_spy/outputs/file.rb +33 -0
- data/lib/dependency_spy/outputs/stdout.rb +27 -0
- data/lib/dependency_spy/semver.rb +71 -0
- data/lib/dependency_spy/version.rb +21 -0
- metadata +246 -0
@@ -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
|