dependabot-npm_and_yarn 0.194.1 → 0.196.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c0a04b2bdbf45e9bca7febc2052b4534d01c00a8bf3d7d893afb87ac2b5960b7
4
- data.tar.gz: 8d6aa071258257451c443b771bd212fe6ee38ef02b776a1f603574f6049c3bf7
3
+ metadata.gz: f571bc3ea2061c9be74632412d008fa2cf20ce7301ca135bd44691e0c71568a6
4
+ data.tar.gz: 8de83c33bc6b953238a25be305e770d1ecd93997f9ef3591ccc99d9e2d890e53
5
5
  SHA512:
6
- metadata.gz: 5ee4014411ebfb7856f621cef550996122f20df078a94536dbc58ef3876604d8a95cb80646f5689c675e5ac3287e8a67bc81e5a0a86b974a58f539236f7016d9
7
- data.tar.gz: fcf294442c74aec5c286f3f2ea5cf0c39f30193623325b621f93fb133df84a9654b032190c07dcc2dae5be5d6d5582f26209709f40443b2a28950192d3e3e0e2
6
+ metadata.gz: d3f7032e974091c5968bf89dba3782fecc0eb31e888764e64cf5c38512d1cf41952a4059866c2e6fe9c17d6413ec0efa0cc256f7506b20097b53774d70367fe3
7
+ data.tar.gz: 5bf66a58afc71a31bbbe1926040fc1a7c2f02af40434695b5538987d67be0d0e8ab8dc35d0ce0ddac184b37a61156886f6ce22c0bc7b1933d2e5bd4be54f2ea8
@@ -1,6 +1,9 @@
1
1
  const conflictingDependencyParser = require("./conflicting-dependency-parser");
2
+ const vulnerabilityAuditor = require("./vulnerability-auditor");
2
3
 
3
4
  module.exports = {
4
5
  findConflictingDependencies:
5
6
  conflictingDependencyParser.findConflictingDependencies,
7
+ vulnerabilityAuditor:
8
+ vulnerabilityAuditor.findVulnerableDependencies,
6
9
  };
@@ -0,0 +1,206 @@
1
+ /* Vulnerability auditor for npm
2
+ *
3
+ * Inputs:
4
+ * - path to directory containing a package.json and a package-lock.json
5
+ * - array of security advisories to audit for,
6
+ * [
7
+ * { dependency_name: string, affected_versions: [string, ...] },
8
+ * ...
9
+ * ]
10
+ *
11
+ * Outputs:
12
+ * - object indicating whether a fix is available and how to achieve it,
13
+ * {
14
+ * dependency_name: string,
15
+ * current_version: string,
16
+ * target_version: string,
17
+ * fix_available: boolean | object,
18
+ * fix_updates: [
19
+ * {
20
+ * dependency_name: string,
21
+ * current_version: string,
22
+ * target_version: string
23
+ * },
24
+ * ...
25
+ * ]
26
+ * }
27
+ */
28
+
29
+ const Arborist = require('@npmcli/arborist')
30
+ const nock = require('nock')
31
+ const { promisify } = require('util');
32
+ const exec = promisify(require('child_process').exec)
33
+
34
+ async function findVulnerableDependencies(directory, advisories) {
35
+ const npmConfig = await loadNpmConfig()
36
+ const caCerts = loadCACerts(npmConfig)
37
+
38
+ const arb = new Arborist({
39
+ path: directory,
40
+ auditRegistry: 'http://localhost:9999',
41
+ ca: caCerts,
42
+ force: true,
43
+ dryRun: true,
44
+ })
45
+
46
+ const scope = nock('http://localhost:9999')
47
+ .persist()
48
+ .post('/-/npm/v1/security/advisories/bulk')
49
+ .reply(200, convertAdvisoriesToRegistryBulkFormat(advisories))
50
+
51
+ if (!nock.isActive()) {
52
+ nock.activate()
53
+ }
54
+
55
+ try {
56
+ const name = advisories[0].dependency_name
57
+ const auditReport = await arb.audit()
58
+ const vuln = auditReport.get(name)
59
+ const version = [...vuln.nodes][0].version
60
+ const fixAvailable = vuln.fixAvailable
61
+ const response = {
62
+ dependency_name: name,
63
+ current_version: version,
64
+ fix_available: Boolean(fixAvailable),
65
+ fix_updates: [],
66
+ }
67
+
68
+ if (!Boolean(fixAvailable)) {
69
+ return response
70
+ }
71
+
72
+ const chains = buildDependencyChains(auditReport, name)
73
+
74
+ // In order to consider the vuln dependency in question fixable,
75
+ // all dependency chains originating from it must be fixable.
76
+ if (chains.some((chain) => !Boolean(chain.fixAvailable))) {
77
+ response.fix_available = false
78
+ return response
79
+ }
80
+
81
+ for (const chain of chains) {
82
+ const topMost = chain.items[0]
83
+ if (topMost.name === name) {
84
+ continue
85
+ }
86
+ response.fix_updates.push({
87
+ dependency_name: topMost.name,
88
+ current_version: topMost.version,
89
+ })
90
+ }
91
+
92
+ const fixTree = await arb.audit({
93
+ fix: true,
94
+ })
95
+
96
+ response.target_version = fixTree.children.get(name)?.version
97
+
98
+ for (const update of response.fix_updates) {
99
+ update.target_version =
100
+ fixTree.children.get(update.dependency_name)?.version
101
+ }
102
+
103
+ return response
104
+ } finally {
105
+ nock.cleanAll()
106
+ nock.restore()
107
+ }
108
+ }
109
+
110
+ function convertAdvisoriesToRegistryBulkFormat(advisories) {
111
+ // npm audit differentiates advisories by `id`. In order to prevent
112
+ // advisories from being clobbered, we maintain a counter so that each
113
+ // advisory gets a unique `id`.
114
+ let nextAdvisoryId = 1
115
+
116
+ return advisories.reduce((formattedAdvisories, advisory) => {
117
+ if (!formattedAdvisories[advisory.dependency_name]) {
118
+ formattedAdvisories[advisory.dependency_name] = []
119
+ }
120
+ let formattedVersions =
121
+ advisory.affected_versions.reduce((memo, version) => {
122
+ memo.push({
123
+ id: nextAdvisoryId++,
124
+ vulnerable_versions: version
125
+ })
126
+ return memo
127
+ }, [])
128
+ formattedAdvisories[advisory.dependency_name].push(...formattedVersions)
129
+ return formattedAdvisories
130
+ }, {})
131
+ }
132
+
133
+ /* Traverses all effects originating from the named dependency in the
134
+ * audit report and builds an array of all dependency chains,
135
+ * [
136
+ * {
137
+ * fixAvailable: true | false | object,
138
+ * items: [
139
+ * { name: 'foo', version: '1.0.0' },
140
+ * ...
141
+ * ]
142
+ * },
143
+ * ...
144
+ * ]
145
+ *
146
+ * The first item in the chain is always the top-most dependency affected by
147
+ * the vulnerable dependency in question. The `fixAvailable` field
148
+ * applies to the first item in the chain (if that item is fixable, then
149
+ * every item after it must be fixable, too).
150
+ */
151
+ function buildDependencyChains(auditReport, name, chain = { items: [] }) {
152
+ const vuln = auditReport.get(name)
153
+ const version = [...vuln.nodes][0].version
154
+ const item = { name, version }
155
+
156
+ if (!vuln.effects.size) {
157
+ // If the current vuln has no effects, we've reached the end of this chain.
158
+ return [{ fixAvailable: vuln.fixAvailable, items: [item, ...chain.items] }]
159
+ }
160
+
161
+ return [...vuln.effects].reduce((chains, effect) => {
162
+ return chains.concat(
163
+ buildDependencyChains(
164
+ auditReport, effect.name, { items: [item, ...chain.items] }))
165
+ }, [])
166
+ }
167
+
168
+ async function loadNpmConfig() {
169
+ const configOutput = await exec('npm config ls --json')
170
+ return JSON.parse(configOutput.stdout)
171
+ }
172
+
173
+ // sourced from npm's cli/lib/utils/config/definitions.js for reading certs from the cafile option
174
+ const fs = require('fs')
175
+ const maybeReadFile = file => {
176
+ try {
177
+ return fs.readFileSync(file, 'utf8')
178
+ } catch (er) {
179
+ if (er.code !== 'ENOENT') {
180
+ throw er
181
+ }
182
+ return null
183
+ }
184
+ }
185
+
186
+ function loadCACerts(npmConfig) {
187
+ if (npmConfig.ca) {
188
+ return npmConfig.ca
189
+ }
190
+
191
+ if (!npmConfig.cafile) {
192
+ return
193
+ }
194
+
195
+ const raw = maybeReadFile(npmConfig.cafile)
196
+ if (!raw) {
197
+ return
198
+ }
199
+
200
+ const delim = '-----END CERTIFICATE-----'
201
+ return raw.replace(/\r\n/g, '\n').split(delim)
202
+ .filter(section => section.trim())
203
+ .map(section => section.trimStart() + delim)
204
+ }
205
+
206
+ module.exports = { findVulnerableDependencies }