dependabot-npm_and_yarn 0.205.1 → 0.206.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: d7b367f078e9c5ac0a29c0a7d7bc2f541e60c2e39c1813cf04d50d3d5fb60f1a
4
- data.tar.gz: 91063ae048150cb001487cfc4415b2aab20a7887e975e86c3632af9065ac5a65
3
+ metadata.gz: 982bada4a5fa7c5684307f5788312a2950d623f698093e5d37ec681020f5dd25
4
+ data.tar.gz: f6b002ff019673222ba9e0c5a945eae620d906a6f57732130697c682b1b5d5ee
5
5
  SHA512:
6
- metadata.gz: 37ef889be3aa66ad6d196543d289d38ee7ec8df99d27ae91cbdd49368d0374120f0364e7ca64a3330ae34e3a3bdf5b3edb60c839a872c962f49c2fd8721ad716
7
- data.tar.gz: f49fcdf6cd6e2b266a02a13b2558dd8c542fd02c8354f72fecea6c5183d6797f5e28f99d208e0f32f57ec269e72d3de085c254f1edba481d6a0145bc11fde68a
6
+ metadata.gz: 884b55700d38d5f99952e8d3bb84968549b5384af0ad5ce69d64e4dc1f957eaaee5988c5fc41473c9fda406c9a3c8f712b8132a4ae04da215f7590984115eaab
7
+ data.tar.gz: eacdea94863a73e095719a2f3bec5616b8ee8c740dfedbb7f188b98b8666abd86c5ae099a809d893c2c16bdde3637b47c56b1eec06e0207efa06245593adf68a
@@ -28,7 +28,7 @@
28
28
 
29
29
  const Arborist = require('@npmcli/arborist')
30
30
  const nock = require('nock')
31
- const { inspect, promisify } = require('util');
31
+ const { promisify } = require('util');
32
32
  const exec = promisify(require('child_process').exec)
33
33
 
34
34
  async function findVulnerableDependencies(directory, advisories) {
@@ -59,16 +59,25 @@ async function findVulnerableDependencies(directory, advisories) {
59
59
 
60
60
  try {
61
61
  const name = advisories[0].dependency_name
62
- const auditReport = await arb.audit()
63
- const vuln = auditReport.get(name)
64
- const version = [...vuln.nodes][0].version
65
- const fixAvailable = vuln.fixAvailable
66
62
  const response = {
67
63
  dependency_name: name,
68
- current_version: version,
69
- fix_available: Boolean(fixAvailable),
70
64
  fix_updates: [],
65
+ top_level_ancestors: [],
66
+ }
67
+ const auditReport = await arb.audit()
68
+ if (!auditReport.has(name)) {
69
+ if (auditReport.tree.children.has(name)) {
70
+ response.current_version = auditReport.tree.children.get(name).version
71
+ }
72
+ response.fix_available = false
73
+ return response
71
74
  }
75
+ const vuln = auditReport.get(name)
76
+ const version = [...vuln.nodes][0].version
77
+ const fixAvailable = vuln.fixAvailable
78
+
79
+ response.current_version = version
80
+ response.fix_available = Boolean(fixAvailable)
72
81
 
73
82
  if (!Boolean(fixAvailable)) {
74
83
  return response
@@ -76,24 +85,41 @@ async function findVulnerableDependencies(directory, advisories) {
76
85
 
77
86
  const chains = buildDependencyChains(auditReport, name)
78
87
 
79
- // In order to consider the vuln dependency in question fixable,
88
+ // In order for the vuln dependency in question to be considered fixable,
80
89
  // all dependency chains originating from it must be fixable.
81
90
  if (chains.some((chain) => !Boolean(chain.fixAvailable))) {
82
91
  response.fix_available = false
83
92
  return response
84
93
  }
85
94
 
86
- for (const chain of chains) {
87
- const topMost = chain.items[0]
88
- if (topMost.name === name) {
89
- continue
90
- }
95
+ const groupedFixUpdateChains = groupBy(chains, (chain) => chain.nodes[0].pkgid)
96
+ let topLevelAncestors = new Set()
97
+
98
+ for (const group of groupedFixUpdateChains.values()) {
99
+ const fixUpdateNode = group[0].nodes[0]
100
+ const groupTopLevelAncestors = group.reduce((anc, chain) => {
101
+ const topLevelNode = chain.nodes[chain.nodes.length - 1]
102
+ return anc.add(topLevelNode.name)
103
+ }, new Set())
104
+
105
+ // Add group's top-level ancestors to the set of all top-level ancestors of
106
+ // the vuln dependency in question.
107
+ topLevelAncestors = new Set([...topLevelAncestors, ...groupTopLevelAncestors])
108
+
109
+ // If a chain consists of only one node, chain.nodes[0].name == chain.nodes[chain.nodes.length-1].name.
110
+ // In such cases, don't include the fix update node as an ancestor of itself.
111
+ const fixUpdateNodeTopLevelAncestors =
112
+ [...groupTopLevelAncestors].filter((nodeName) => nodeName !== fixUpdateNode.name).sort()
113
+
91
114
  response.fix_updates.push({
92
- dependency_name: topMost.name,
93
- current_version: topMost.version,
115
+ dependency_name: fixUpdateNode.name,
116
+ current_version: fixUpdateNode.version,
117
+ top_level_ancestors: fixUpdateNodeTopLevelAncestors,
94
118
  })
95
119
  }
96
120
 
121
+ response.top_level_ancestors = [...topLevelAncestors].sort()
122
+
97
123
  const fixTree = await arb.audit({
98
124
  fix: true,
99
125
  })
@@ -135,75 +161,60 @@ function convertAdvisoriesToRegistryBulkFormat(advisories) {
135
161
  }, {})
136
162
  }
137
163
 
138
- /* Traverses all effects originating from the named dependency in the
139
- * audit report and returns an array of dependency chains rooted in the named
140
- * dependency,
164
+ /* Returns an array of dependency chains rooted in the named dependency,
141
165
  * [
142
166
  * {
143
167
  * fixAvailable: true | false | object,
144
- * items: [
145
- * { name: 'foo', version: '1.0.0' },
168
+ * nodes: [
169
+ * ArboristNode {
170
+ * name: 'foo',
171
+ * version: '1.0.0',
172
+ * ...
173
+ * },
146
174
  * ...
147
175
  * ]
148
176
  * },
149
177
  * ...
150
178
  * ]
151
179
  *
152
- * The first item in the chain is always the top-most dependency affected by
153
- * the vulnerable dependency in question. The `fixAvailable` field
154
- * applies to the first item in the chain (if that item is fixable, then
155
- * every item after it must be fixable, too).
180
+ * The first node in each chain is the innermost dependency affected by the vuln;
181
+ * the `fixAvailable` field applies to this dependency. The last node in each
182
+ * chain is always a top-level dependency.
156
183
  */
157
184
  function buildDependencyChains(auditReport, name) {
158
- const helper = (name, chain, visited) => {
159
- // The vuln for this dependency.
160
- const vuln = auditReport.get(name)
161
-
162
- // The current version of this dependency.
163
- const version = [...vuln.nodes][0].version
164
-
165
- // The item that will represent this dependency in this chain.
166
- const item = { name, version }
167
-
168
- // Array of effects, excluding cycles.
169
- const effects = [...vuln.effects]
170
-
171
- if (visited.has(name)) {
172
- // We've already visited this dependency in this chain, so we've detected a cycle.
173
- // We currently throw when this happens. Ultimately we want to gracefully handle
174
- // cycles and still return the recommended fix updates.
175
- const source = chain.items[chain.items.length-1]
176
- const message = `Cycle detected while traversing effects from ` +
177
- `${source.name}@${source.version}: ` +
178
- inspect([name, ...visited], {
179
- breakLength: Infinity,
180
- depth: 1,
181
- maxStringLength: 255,
182
- })
183
- throw new Error(message)
185
+ const helper = (node, chain, visited) => {
186
+ if (!node) {
187
+ return []
184
188
  }
185
-
186
- if (!effects.length) {
187
- // If the current vuln has no effects, we've reached the end of this chain.
188
- return [{ fixAvailable: vuln.fixAvailable, items: [item, ...chain.items] }]
189
+ if (visited.has(node.name)) {
190
+ // We've already seen this node; end path.
191
+ return []
189
192
  }
190
-
191
- return effects.reduce((chains, effect) => {
192
- return chains.concat(
193
- helper(effect.name, { items: [item, ...chain.items] }, new Set([name, ...visited])))
193
+ if (auditReport.has(node.name)) {
194
+ const vuln = auditReport.get(node.name)
195
+ return [{ fixAvailable: vuln.fixAvailable, nodes: [node, ...chain.nodes] }]
196
+ }
197
+ if (!node.edgesOut.size) {
198
+ // This is a leaf node that is unaffected by the vuln; end path.
199
+ return []
200
+ }
201
+ return [...node.edgesOut.values()].reduce((chains, { to }) => {
202
+ // Only prepend current node to chain/visited if it's not the project root.
203
+ const newChain = node.isProjectRoot ? chain : { nodes: [node, ...chain.nodes] }
204
+ const newVisited = node.isProjectRoot ? visited : new Set([node.name, ...visited])
205
+ return chains.concat(helper(to, newChain, newVisited))
194
206
  }, [])
195
207
  }
208
+ return helper(auditReport.tree, { nodes: [] }, new Set())
209
+ }
196
210
 
197
- const chains = helper(name, { items: [] }, new Set())
198
- const seen = new Set()
199
- return chains.filter(chain => {
200
- const head = chain.items[0]
201
- if (seen.has(head.name)) {
202
- return false
203
- }
204
- seen.add(head.name)
205
- return true
206
- })
211
+ function groupBy(elems, fn) {
212
+ const groups = new Map()
213
+ for (const [index, elem] of [...elems].entries()) {
214
+ const key = fn(elem, index, elems)
215
+ groups.set(key, (groups.get(key) || []).concat([elem]))
216
+ }
217
+ return groups
207
218
  }
208
219
 
209
220
  async function loadNpmConfig() {
@@ -36,11 +36,16 @@ module Dependabot
36
36
  # * :dependency_name [String] the name of the blocking dependency
37
37
  # * :current_version [String] the current version of the blocking dependency
38
38
  # * :target_version [String] the target version of the blocking dependency
39
+ # * :top_level_ancestors [Array<String>] the names of top-level dependencies with a transitive
40
+ # dependency on the blocking dependency
41
+ # * :top_level_ancestors [Array<String>] the names of all top-level dependencies with a transitive
42
+ # dependency on the dependency
39
43
  def audit(dependency:, security_advisories:)
40
44
  fix_unavailable = {
41
45
  "dependency_name" => dependency.name,
42
46
  "fix_available" => false,
43
- "fix_updates" => []
47
+ "fix_updates" => [],
48
+ "top_level_ancestors" => []
44
49
  }
45
50
 
46
51
  SharedHelpers.in_a_temporary_directory do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-npm_and_yarn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.205.1
4
+ version: 0.206.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-08 00:00:00.000000000 Z
11
+ date: 2022-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dependabot-common
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 0.205.1
19
+ version: 0.206.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 0.205.1
26
+ version: 0.206.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: debase
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -128,14 +128,14 @@ dependencies:
128
128
  requirements:
129
129
  - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: 1.31.2
131
+ version: 1.33.0
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
- version: 1.31.2
138
+ version: 1.33.0
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: ruby-debug-ide
141
141
  requirement: !ruby/object:Gem::Requirement