dependabot-npm_and_yarn 0.195.0 → 0.196.0

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