dependabot-npm_and_yarn 0.195.0 → 0.196.0

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: 331f32c7de63c0e1d6fba3c5fa07440bd1a2ecbd7b4bf6eb9fc5bc7f65df961b
4
- data.tar.gz: b5ca5c033cab29f1bb7ad956777bf209825a9cba3632f2cf69ae2635f35408d1
3
+ metadata.gz: 3461d61de14b92511754889ae9a9a5edafe20a6967037ec7946c5f6ec33e3615
4
+ data.tar.gz: f334af6509d4cb9e1afdcc9d06db1b5f200b597a5af4d2f617a0005922577677
5
5
  SHA512:
6
- metadata.gz: 35aea567a10c997e1c6b231da5d8f3042dd5ce41182a750de50ed9f3d9e5852f578bcb2d9e5c75d6bef4f9280d2d64d85852315634ab3c15d4e0c30fc7fa81c7
7
- data.tar.gz: c1ba801b5b44b4a813baad59f6e5b1de4e4df1112178bda5025efac571fadbf7fbb18149d0f58dcfe3e69ab7b9215f16dc6aa5551e27c8dd83bd3b41c15aefdb
6
+ metadata.gz: 880b9e022bca930c2b0f4b66d7e56c381739682893a5aa209f391d668da544dcab947d8ff5fd319fe39cdf1686864e47f191431d476cf8224ecea314689d3f5a
7
+ data.tar.gz: 322da33f31ae13dee139041931b7c5e6a7ac9df4947679e1d00d413920b2cd9aae7e041247400ec265a17103a77870f04177926ea51985237c59cb3412671617
@@ -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 }