package-audit 0.8.2 → 0.9.1
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.
- checksums.yaml +4 -4
- data/README.md +16 -19
- data/lib/package/audit/const/cmd.rb +1 -1
- data/lib/package/audit/npm/node_collection.rb +17 -11
- data/lib/package/audit/npm/package_lock_parser.rb +80 -0
- data/lib/package/audit/npm/semver_range.rb +138 -0
- data/lib/package/audit/npm/vulnerability_finder.rb +18 -19
- data/lib/package/audit/ruby/gem_meta_data.rb +1 -1
- data/lib/package/audit/ruby/vulnerability_finder.rb +3 -1
- data/lib/package/audit/services/config_cleaner.rb +4 -1
- data/lib/package/audit/version.rb +1 -1
- metadata +8 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f5f9a3b6c50fcbebe1832c0559eddcd5a4e99c94779d1a251ad00c540df6c6b6
|
|
4
|
+
data.tar.gz: 84c02793f8d4b6a07099179fa5deee0ee473541668eb4b9212ea62b0e61d37d5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 197beea22db4bdc21106d235e6bf0d360d61c396498d516a1fbd8aa8cca2342348aa4bcdacbd08376199ad0ff0a0555f0e145a9d2d19d4a6e1e4f29a684c6a4f
|
|
7
|
+
data.tar.gz: 8540a1858d4ad3a094f20d77c174dafcbffe1b3af1fc1a9ead5806443efb314a7bbbcb77d7fb3ccc764aed0f921de9e71b588ea82283b06a4cf4bb1dfb3fc414
|
data/README.md
CHANGED
|
@@ -4,18 +4,16 @@
|
|
|
4
4
|
[](https://github.com/vkononov/package-audit/blob/main/.github/workflows/test.yml)
|
|
5
5
|
[](https://github.com/vkononov/package-audit/actions/workflows/test.yml)
|
|
6
6
|
[](https://github.com/vkononov/package-audit/actions/workflows/lint.yml)
|
|
7
|
-
[](https://opensource.org/
|
|
7
|
+
[](https://opensource.org/license/MIT)
|
|
8
8
|
|
|
9
9
|
A useful tool for patch management and prioritization, `package-audit` produces a list of dependencies that are outdated, deprecated or have security vulnerabilities.
|
|
10
10
|
|
|
11
11
|
Supports **Ruby 2.6 → latest**, with the full Ruby matrix tested daily in CI.
|
|
12
12
|
|
|
13
|
-
[](https://www.buymeacoffee.com/vkononov)
|
|
14
|
-
|
|
15
13
|
## Supported Technologies
|
|
16
14
|
|
|
17
15
|
* Ruby
|
|
18
|
-
* Node (using Yarn)
|
|
16
|
+
* Node (using npm or Yarn)
|
|
19
17
|
|
|
20
18
|
## Report Example
|
|
21
19
|
|
|
@@ -75,6 +73,9 @@ Show only specific risk types:
|
|
|
75
73
|
# Show only deprecated packages
|
|
76
74
|
package-audit [DIR] --deprecated
|
|
77
75
|
|
|
76
|
+
# Show only outdated packages
|
|
77
|
+
package-audit [DIR] --outdated
|
|
78
|
+
|
|
78
79
|
# Show only vulnerable packages
|
|
79
80
|
package-audit [DIR] --vulnerable
|
|
80
81
|
|
|
@@ -93,24 +94,15 @@ package-audit [DIR] --skip-deprecated
|
|
|
93
94
|
# Show everything except outdated packages
|
|
94
95
|
package-audit [DIR] --skip-outdated
|
|
95
96
|
|
|
97
|
+
# Show everything except vulnerable packages
|
|
98
|
+
package-audit [DIR] --skip-vulnerable
|
|
99
|
+
|
|
96
100
|
# Show only vulnerable packages (exclude deprecated and outdated)
|
|
97
101
|
package-audit [DIR] --skip-deprecated --skip-outdated
|
|
98
102
|
```
|
|
99
103
|
|
|
100
104
|
**Important:** Packages with multiple risk types are handled intelligently. For example, a package that is both outdated and vulnerable will still appear when using `--skip-outdated` because it has a vulnerability.
|
|
101
105
|
|
|
102
|
-
### Understanding the Flags Column
|
|
103
|
-
|
|
104
|
-
The Flags column in the output shows which risk types apply to each package:
|
|
105
|
-
|
|
106
|
-
- `⦗V··⦘` - Vulnerable (has security vulnerabilities)
|
|
107
|
-
- `⦗·O·⦘` - Outdated (newer version available)
|
|
108
|
-
- `⦗··D⦘` - Deprecated (no updates in 2+ years)
|
|
109
|
-
- `⦗VO·⦘` - Both vulnerable and outdated
|
|
110
|
-
- `⦗VOD⦘` - All three risk types apply
|
|
111
|
-
|
|
112
|
-
The footer uses the same notation `⦗V⦘ulnerable, ⦗O⦘utdated, ⦗D⦘eprecated` as a legend.
|
|
113
|
-
|
|
114
106
|
### Scoping Options
|
|
115
107
|
|
|
116
108
|
Filter by technology or group:
|
|
@@ -144,11 +136,14 @@ package-audit [DIR] --include-ignored
|
|
|
144
136
|
### Output Formats
|
|
145
137
|
|
|
146
138
|
```bash
|
|
147
|
-
# CSV format
|
|
139
|
+
# CSV format (-f is an alias for --format)
|
|
148
140
|
package-audit [DIR] --format csv
|
|
149
141
|
|
|
150
142
|
# Markdown format
|
|
151
143
|
package-audit [DIR] --format md
|
|
144
|
+
|
|
145
|
+
# CSV without the header row
|
|
146
|
+
package-audit [DIR] --format csv --exclude-headers
|
|
152
147
|
```
|
|
153
148
|
|
|
154
149
|
### Other Commands
|
|
@@ -157,7 +152,7 @@ package-audit [DIR] --format md
|
|
|
157
152
|
# Show how risk is calculated
|
|
158
153
|
package-audit risk
|
|
159
154
|
|
|
160
|
-
# Show version
|
|
155
|
+
# Show version (-v and --version also work)
|
|
161
156
|
package-audit version
|
|
162
157
|
```
|
|
163
158
|
|
|
@@ -172,6 +167,8 @@ package-audit help [COMMAND]
|
|
|
172
167
|
|
|
173
168
|
The `package-audit` gem automatically searches for `.package-audit.yml` in the current directory or in the specified `DIR` if available. However, you have the option to override the default configuration file location by using the `--config` (or `-c`) flag.
|
|
174
169
|
|
|
170
|
+
When `--technology` is specified, stale exclusions are cleaned up only for the selected technologies. Configuration entries for other technologies are preserved.
|
|
171
|
+
|
|
175
172
|
#### Below is an example of a configuration file:
|
|
176
173
|
|
|
177
174
|
```YAML
|
|
@@ -215,4 +212,4 @@ technology:
|
|
|
215
212
|
|
|
216
213
|
## License
|
|
217
214
|
|
|
218
|
-
The gem is available as open source under the terms of the [MIT License](
|
|
215
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/license/MIT).
|
|
@@ -3,7 +3,7 @@ module Package
|
|
|
3
3
|
module Const
|
|
4
4
|
module Cmd
|
|
5
5
|
BUNDLE_AUDIT = 'bundle-audit check --update'
|
|
6
|
-
BUNDLE_AUDIT_JSON =
|
|
6
|
+
BUNDLE_AUDIT_JSON = %w[bundle-audit check --update --quiet --format json].freeze
|
|
7
7
|
|
|
8
8
|
NPM_AUDIT = 'npm audit'
|
|
9
9
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require_relative '../const/file'
|
|
2
2
|
require_relative '../services/duplicate_package_merger'
|
|
3
3
|
require_relative 'npm_meta_data'
|
|
4
|
+
require_relative 'package_lock_parser'
|
|
4
5
|
require_relative 'vulnerability_finder'
|
|
5
6
|
require_relative 'yarn_lock_parser'
|
|
6
7
|
|
|
@@ -73,22 +74,27 @@ module Package
|
|
|
73
74
|
dependencies.reject { |_name, version| local_dependency?(version) }
|
|
74
75
|
end
|
|
75
76
|
|
|
77
|
+
# Packages that live in the project itself (paths, workspaces, portals)
|
|
78
|
+
# are not published to the registry and cannot be audited against it.
|
|
76
79
|
def local_dependency?(version)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
version.to_s.include?('file:') ||
|
|
80
|
-
# Check for git repositories with local paths
|
|
81
|
-
(version.to_s.start_with?('git+') && version.to_s.include?('file:'))
|
|
80
|
+
version.to_s.start_with?('file:', 'link:', 'workspace:', 'portal:', './', '../') ||
|
|
81
|
+
version.to_s.include?('file:')
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
def fetch_from_lock_file
|
|
85
85
|
default_deps, dev_deps, resolutions = fetch_from_package_json
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
86
|
+
parser = lock_file_parser
|
|
87
|
+
return [] if parser.nil?
|
|
88
|
+
|
|
89
|
+
parser.fetch(default_deps || {}, dev_deps || {}, resolutions || {})
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def lock_file_parser
|
|
93
|
+
yarn_lock = "#{@dir}/#{Const::File::YARN_LOCK}"
|
|
94
|
+
return YarnLockParser.new(yarn_lock) if File.exist?(yarn_lock)
|
|
95
|
+
|
|
96
|
+
package_lock = "#{@dir}/#{Const::File::PACKAGE_LOCK_JSON}"
|
|
97
|
+
PackageLockParser.new(package_lock) if File.exist?(package_lock)
|
|
92
98
|
end
|
|
93
99
|
end
|
|
94
100
|
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
require_relative '../enum/group'
|
|
4
|
+
require_relative '../models/package'
|
|
5
|
+
|
|
6
|
+
# Ruby 2.7 added this. A missing lockfile entry raises it, same as the yarn parser.
|
|
7
|
+
Object.const_set(:NoMatchingPatternError, Class.new(StandardError)) unless defined?(NoMatchingPatternError)
|
|
8
|
+
|
|
9
|
+
module Package
|
|
10
|
+
module Audit
|
|
11
|
+
module Npm
|
|
12
|
+
# Reads package-lock.json (lockfileVersion 1, 2 and 3) and resolves the
|
|
13
|
+
# installed version of each direct dependency.
|
|
14
|
+
class PackageLockParser
|
|
15
|
+
NODE_MODULES = 'node_modules/'
|
|
16
|
+
|
|
17
|
+
def initialize(package_lock_path)
|
|
18
|
+
@package_lock_path = package_lock_path
|
|
19
|
+
@lock = JSON.parse(File.read(package_lock_path))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def fetch(default_deps, dev_deps, _resolutions = {})
|
|
23
|
+
default_deps.merge(dev_deps).map do |dep_name, _expected_version|
|
|
24
|
+
version = direct_version(dep_name)
|
|
25
|
+
raise NoMatchingPatternError, "Unable to find \"#{dep_name}\" in #{@package_lock_path}" if version.nil?
|
|
26
|
+
|
|
27
|
+
pkg = Package.new(dep_name.to_s, version, 'node')
|
|
28
|
+
pkg.update groups: package_groups(dep_name, dev_deps)
|
|
29
|
+
pkg
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Every installed package, including transitive ones, as [name, version] pairs.
|
|
34
|
+
# Entries outside node_modules are workspace sources, not registry packages.
|
|
35
|
+
def all_packages
|
|
36
|
+
if @lock.key?('packages')
|
|
37
|
+
@lock['packages'].map do |path, entry|
|
|
38
|
+
next if entry['version'].nil? || !path.include?(NODE_MODULES)
|
|
39
|
+
|
|
40
|
+
[name_from_path(path), entry['version']]
|
|
41
|
+
end.compact
|
|
42
|
+
else
|
|
43
|
+
walk_v1(@lock['dependencies'] || {})
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def direct_version(dep_name)
|
|
50
|
+
if @lock.key?('packages')
|
|
51
|
+
@lock.dig('packages', "#{NODE_MODULES}#{dep_name}", 'version')
|
|
52
|
+
else
|
|
53
|
+
@lock.dig('dependencies', dep_name, 'version')
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def package_groups(dep_name, dev_deps)
|
|
58
|
+
if dev_deps.key?(dep_name)
|
|
59
|
+
[Enum::Group::DEV]
|
|
60
|
+
else
|
|
61
|
+
[Enum::Group::DEFAULT, Enum::Group::DEV]
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# "node_modules/a/node_modules/@scope/b" => "@scope/b"
|
|
66
|
+
def name_from_path(path)
|
|
67
|
+
path.split(NODE_MODULES).last
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def walk_v1(dependencies, packages = [])
|
|
71
|
+
dependencies.each do |name, entry|
|
|
72
|
+
packages << [name, entry['version']] unless entry['version'].nil?
|
|
73
|
+
walk_v1(entry['dependencies'] || {}, packages)
|
|
74
|
+
end
|
|
75
|
+
packages
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
module Package
|
|
2
|
+
module Audit
|
|
3
|
+
module Npm
|
|
4
|
+
# Evaluates npm semver ranges (as used in registry advisories) against an
|
|
5
|
+
# installed version. Supports comparator sets joined by "||", hyphen
|
|
6
|
+
# ranges, caret and tilde ranges, and x-ranges. A range that cannot be
|
|
7
|
+
# understood is treated as matching, so an advisory is never silently
|
|
8
|
+
# dropped because of unusual syntax.
|
|
9
|
+
class SemverRange
|
|
10
|
+
WILDCARDS = %w[* x X].freeze
|
|
11
|
+
|
|
12
|
+
def initialize(range)
|
|
13
|
+
@range = range.to_s.strip
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def satisfied_by?(version)
|
|
17
|
+
return true if @range.empty?
|
|
18
|
+
|
|
19
|
+
installed = parse_version(version)
|
|
20
|
+
return false if installed.nil?
|
|
21
|
+
|
|
22
|
+
@range.split('||').any? { |set| comparator_set_matches?(set.strip, installed) }
|
|
23
|
+
rescue ArgumentError
|
|
24
|
+
true
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def comparator_set_matches?(set, installed)
|
|
30
|
+
return true if set.empty? || WILDCARDS.include?(set)
|
|
31
|
+
|
|
32
|
+
Gem::Requirement.new(*requirements_for(set)).satisfied_by?(installed)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def requirements_for(set)
|
|
36
|
+
return hyphen_requirements(*set.split(/\s+-\s+/, 2)) if set.match?(/\s-\s/)
|
|
37
|
+
|
|
38
|
+
set.split(/\s+/).flat_map { |comparator| comparator_requirements(comparator) }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def comparator_requirements(comparator)
|
|
42
|
+
case comparator
|
|
43
|
+
when /\A\^(.+)\z/ then caret_requirements(Regexp.last_match(1))
|
|
44
|
+
when /\A~>?(.+)\z/ then tilde_requirements(Regexp.last_match(1))
|
|
45
|
+
when /\A(>=|<=|>|<|=)\s*(.+)\z/ then operator_requirements(Regexp.last_match(1), Regexp.last_match(2))
|
|
46
|
+
else x_range_requirements(comparator)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def operator_requirements(operator, partial)
|
|
51
|
+
parts = version_parts(partial)
|
|
52
|
+
return [] if parts.empty?
|
|
53
|
+
return ["#{operator} #{normalize(partial)}"] if parts.length == 3 || partial.include?('-')
|
|
54
|
+
|
|
55
|
+
partial_operator_requirements(operator, fill(parts), bump(parts))
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# ">=1.2" means >=1.2.0, ">1.2" means >=1.3.0, "<=1.2" means <1.3.0, "=1.2" means 1.2.x
|
|
59
|
+
def partial_operator_requirements(operator, lower, upper)
|
|
60
|
+
case operator
|
|
61
|
+
when '>' then [">= #{upper}"]
|
|
62
|
+
when '<=' then ["< #{upper}"]
|
|
63
|
+
when '=' then [">= #{lower}", "< #{upper}"]
|
|
64
|
+
else ["#{operator} #{lower}"]
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def hyphen_requirements(low, high)
|
|
69
|
+
requirements = []
|
|
70
|
+
requirements << ">= #{normalize(low)}" unless version_parts(low).empty?
|
|
71
|
+
high_parts = version_parts(high)
|
|
72
|
+
return requirements if high_parts.empty?
|
|
73
|
+
|
|
74
|
+
requirements << (high_parts.length == 3 ? "<= #{normalize(high)}" : "< #{bump(high_parts)}")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def caret_requirements(partial)
|
|
78
|
+
parts = version_parts(partial)
|
|
79
|
+
return [] if parts.empty?
|
|
80
|
+
|
|
81
|
+
# Bump the leftmost non-zero part, or the last given part when all are zero.
|
|
82
|
+
index = parts.index(&:positive?) || (parts.length - 1)
|
|
83
|
+
[">= #{normalize(partial)}", "< #{bump(parts.first(index + 1))}"]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def tilde_requirements(partial)
|
|
87
|
+
parts = version_parts(partial)
|
|
88
|
+
return [] if parts.empty?
|
|
89
|
+
|
|
90
|
+
upper = parts.length == 1 ? "#{parts[0] + 1}.0.0" : "#{parts[0]}.#{parts[1] + 1}.0"
|
|
91
|
+
[">= #{normalize(partial)}", "< #{upper}"]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def x_range_requirements(partial)
|
|
95
|
+
parts = version_parts(partial)
|
|
96
|
+
return [] if parts.empty?
|
|
97
|
+
return ["= #{normalize(partial)}"] if parts.length == 3 && !partial.match?(/[xX*]/)
|
|
98
|
+
|
|
99
|
+
[">= #{fill(parts)}", "< #{bump(parts)}"]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Numeric leading parts of a version, stopping at the first wildcard.
|
|
103
|
+
# "1.2.x" => [1, 2], "1.2.3-beta" => [1, 2, 3], "x" => []
|
|
104
|
+
def version_parts(partial)
|
|
105
|
+
partial.to_s.sub(/\A[v=]+/, '').sub(/[-+].*\z/, '').split('.').take_while do |part|
|
|
106
|
+
part.match?(/\A\d+\z/)
|
|
107
|
+
end.map(&:to_i)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def fill(parts)
|
|
111
|
+
(parts + [0, 0, 0]).first(3).join('.')
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def bump(parts)
|
|
115
|
+
bumped = parts.dup
|
|
116
|
+
bumped[-1] += 1
|
|
117
|
+
fill(bumped)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Gem::Version understands "1.2.3-beta.1" (as a prerelease) but not build
|
|
121
|
+
# metadata, a leading "v", or missing patch numbers.
|
|
122
|
+
def normalize(version)
|
|
123
|
+
cleaned = version.to_s.sub(/\A[v=]+/, '').sub(/\+.*\z/, '')
|
|
124
|
+
numeric, prerelease = cleaned.split('-', 2)
|
|
125
|
+
numeric = numeric.to_s
|
|
126
|
+
numeric = fill(numeric.split('.').map(&:to_i)) if numeric.match?(/\A\d+(\.\d+){0,2}\z/)
|
|
127
|
+
prerelease ? "#{numeric}-#{prerelease}" : numeric
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def parse_version(version)
|
|
131
|
+
Gem::Version.new(normalize(version))
|
|
132
|
+
rescue ArgumentError
|
|
133
|
+
nil
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -4,6 +4,8 @@ require 'openssl'
|
|
|
4
4
|
|
|
5
5
|
require_relative '../const/file'
|
|
6
6
|
require_relative '../enum/vulnerability_type'
|
|
7
|
+
require_relative 'package_lock_parser'
|
|
8
|
+
require_relative 'semver_range'
|
|
7
9
|
|
|
8
10
|
module Package
|
|
9
11
|
module Audit
|
|
@@ -12,7 +14,7 @@ module Package
|
|
|
12
14
|
BULK_ADVISORY_URL = 'https://registry.npmjs.org/-/npm/v1/security/advisories/bulk'
|
|
13
15
|
BATCH_SIZE = 100
|
|
14
16
|
TIMEOUT = 30
|
|
15
|
-
VERSION_LINE = /^\s+"?version"
|
|
17
|
+
VERSION_LINE = /^\s+"?version"?(?::\s*|\s+)["']?([^"'\s]+)["']?\s*$/
|
|
16
18
|
|
|
17
19
|
def initialize(dir, pkgs)
|
|
18
20
|
@dir = dir
|
|
@@ -51,9 +53,16 @@ module Package
|
|
|
51
53
|
end
|
|
52
54
|
|
|
53
55
|
def lockfile_packages
|
|
54
|
-
|
|
55
|
-
return
|
|
56
|
+
yarn_lock = "#{@dir}/#{Const::File::YARN_LOCK}"
|
|
57
|
+
return yarn_lockfile_packages(yarn_lock) if File.exist?(yarn_lock)
|
|
56
58
|
|
|
59
|
+
package_lock = "#{@dir}/#{Const::File::PACKAGE_LOCK_JSON}"
|
|
60
|
+
return PackageLockParser.new(package_lock).all_packages if File.exist?(package_lock)
|
|
61
|
+
|
|
62
|
+
[]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def yarn_lockfile_packages(path)
|
|
57
66
|
packages = []
|
|
58
67
|
current_names = []
|
|
59
68
|
File.foreach(path) do |line|
|
|
@@ -96,14 +105,13 @@ module Package
|
|
|
96
105
|
uri = URI(BULK_ADVISORY_URL)
|
|
97
106
|
response = make_request(uri, versions_by_name.to_json)
|
|
98
107
|
unless response.is_a?(Net::HTTPSuccess)
|
|
99
|
-
|
|
100
|
-
return {}
|
|
108
|
+
raise "Unable to fetch npm advisories (#{response.code} #{response.message})"
|
|
101
109
|
end
|
|
102
110
|
|
|
103
|
-
JSON.parse(response.body)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
111
|
+
advisories = JSON.parse(response.body)
|
|
112
|
+
raise 'Invalid npm advisory response' unless advisories.is_a?(Hash)
|
|
113
|
+
|
|
114
|
+
advisories
|
|
107
115
|
end
|
|
108
116
|
|
|
109
117
|
def make_request(uri, body)
|
|
@@ -136,16 +144,7 @@ module Package
|
|
|
136
144
|
end
|
|
137
145
|
|
|
138
146
|
def version_matches?(version, vulnerable_versions)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
vulnerable_versions.split(/\s*\|\|\s*/).any? do |range|
|
|
142
|
-
requirements = range.split(/\s+/).reject(&:empty?)
|
|
143
|
-
next true if requirements.empty?
|
|
144
|
-
|
|
145
|
-
Gem::Requirement.new(*requirements).satisfied_by?(Gem::Version.new(version))
|
|
146
|
-
rescue ArgumentError
|
|
147
|
-
true
|
|
148
|
-
end
|
|
147
|
+
SemverRange.new(vulnerable_versions).satisfied_by?(version)
|
|
149
148
|
end
|
|
150
149
|
|
|
151
150
|
def update_vulnerability_info(name, version, severity)
|
|
@@ -11,7 +11,9 @@ module Package
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def run
|
|
14
|
-
json_result =
|
|
14
|
+
json_result = IO.popen(
|
|
15
|
+
[*Const::Cmd::BUNDLE_AUDIT_JSON, @dir.to_s], err: File::NULL, &:read
|
|
16
|
+
)
|
|
15
17
|
vulnerability_json_array = JSON.parse(json_result, symbolize_names: true)[:results]
|
|
16
18
|
vulnerability_json_array.each do |vulnerability_json|
|
|
17
19
|
update_meta_data(vulnerability_json)
|
|
@@ -62,7 +62,10 @@ module Package
|
|
|
62
62
|
cleaned
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
-
def clean_packages_for_technology(technology, packages)
|
|
65
|
+
def clean_packages_for_technology(technology, packages) # rubocop:disable Metrics/MethodLength
|
|
66
|
+
technologies = @options[Enum::Option::TECHNOLOGY]
|
|
67
|
+
return packages if technologies && !technologies.include?(technology)
|
|
68
|
+
|
|
66
69
|
return {} unless packages.is_a?(Hash)
|
|
67
70
|
|
|
68
71
|
current_packages = current_packages_for_technology(technology)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: package-audit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vadim Kononov
|
|
@@ -37,8 +37,9 @@ dependencies:
|
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '1.2'
|
|
40
|
-
description:
|
|
41
|
-
|
|
40
|
+
description: Audits project dependencies across supported package managers and lists
|
|
41
|
+
those that are outdated, deprecated or have known security vulnerabilities, for
|
|
42
|
+
patch prioritization.
|
|
42
43
|
email:
|
|
43
44
|
- vadim@konoson.com
|
|
44
45
|
executables:
|
|
@@ -73,6 +74,8 @@ files:
|
|
|
73
74
|
- lib/package/audit/models/risk.rb
|
|
74
75
|
- lib/package/audit/npm/node_collection.rb
|
|
75
76
|
- lib/package/audit/npm/npm_meta_data.rb
|
|
77
|
+
- lib/package/audit/npm/package_lock_parser.rb
|
|
78
|
+
- lib/package/audit/npm/semver_range.rb
|
|
76
79
|
- lib/package/audit/npm/vulnerability_finder.rb
|
|
77
80
|
- lib/package/audit/npm/yarn_lock_parser.rb
|
|
78
81
|
- lib/package/audit/ruby/bundler_specs.rb
|
|
@@ -114,7 +117,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
114
117
|
- !ruby/object:Gem::Version
|
|
115
118
|
version: '0'
|
|
116
119
|
requirements: []
|
|
117
|
-
rubygems_version: 4.0.
|
|
120
|
+
rubygems_version: 4.0.20
|
|
118
121
|
specification_version: 4
|
|
119
|
-
summary:
|
|
122
|
+
summary: CLI that reports outdated, deprecated and vulnerable dependencies in a project
|
|
120
123
|
test_files: []
|