Skip to content

Commit 4366dba

Browse files
author
Federico Builes
committed
Advisory filters should not drop entire dependencies.
1 parent 50dafeb commit 4366dba

File tree

2 files changed

+118
-29
lines changed

2 files changed

+118
-29
lines changed

__tests__/filter.test.ts

Lines changed: 111 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const npmChange: Change = {
1919
vulnerabilities: [
2020
{
2121
severity: 'critical',
22-
advisory_ghsa_id: 'first-random_string',
22+
advisory_ghsa_id: 'vulnerable-ghsa-id',
2323
advisory_summary: 'very dangerous',
2424
advisory_url: 'github.com/future-funk'
2525
}
@@ -39,13 +39,13 @@ const rubyChange: Change = {
3939
vulnerabilities: [
4040
{
4141
severity: 'moderate',
42-
advisory_ghsa_id: 'second-random_string',
42+
advisory_ghsa_id: 'moderate-ghsa-id',
4343
advisory_summary: 'not so dangerous',
4444
advisory_url: 'github.com/future-funk'
4545
},
4646
{
4747
severity: 'low',
48-
advisory_ghsa_id: 'third-random_string',
48+
advisory_ghsa_id: 'low-ghsa-id',
4949
advisory_summary: 'dont page me',
5050
advisory_url: 'github.com/future-funk'
5151
}
@@ -65,6 +65,64 @@ const noVulnNpmChange: Change = {
6565
vulnerabilities: []
6666
}
6767

68+
const lodashChange: Change = {
69+
change_type: 'added',
70+
manifest: 'package.json',
71+
ecosystem: 'npm',
72+
name: 'lodash',
73+
version: '4.17.0',
74+
package_url: 'pkg:npm/[email protected]',
75+
license: 'MIT',
76+
source_repository_url: 'https://github.com/lodash/lodash',
77+
scope: 'runtime',
78+
vulnerabilities: [
79+
{
80+
severity: 'critical',
81+
advisory_ghsa_id: 'GHSA-jf85-cpcp-j695',
82+
advisory_summary: 'Prototype Pollution in lodash',
83+
advisory_url: 'https://github.com/advisories/GHSA-jf85-cpcp-j695'
84+
},
85+
{
86+
severity: 'high',
87+
advisory_ghsa_id: 'GHSA-4xc9-xhrj-v574',
88+
advisory_summary: 'Prototype Pollution in lodash',
89+
advisory_url: 'https://github.com/advisories/GHSA-4xc9-xhrj-v574'
90+
},
91+
{
92+
severity: 'high',
93+
advisory_ghsa_id: 'GHSA-35jh-r3h4-6jhm',
94+
advisory_summary: 'Command Injection in lodash',
95+
advisory_url: 'https://github.com/advisories/GHSA-35jh-r3h4-6jhm'
96+
},
97+
{
98+
severity: 'high',
99+
advisory_ghsa_id: 'GHSA-p6mc-m468-83gw',
100+
advisory_summary: 'Prototype Pollution in lodash',
101+
advisory_url: 'https://github.com/advisories/GHSA-p6mc-m468-83gw'
102+
},
103+
{
104+
severity: 'moderate',
105+
advisory_ghsa_id: 'GHSA-x5rq-j2xg-h7qm',
106+
advisory_summary:
107+
'Regular Expression Denial of Service (ReDoS) in lodash',
108+
advisory_url: 'https://github.com/advisories/GHSA-x5rq-j2xg-h7qm'
109+
},
110+
{
111+
severity: 'moderate',
112+
advisory_ghsa_id: 'GHSA-29mw-wpgm-hmr9',
113+
advisory_summary:
114+
'Regular Expression Denial of Service (ReDoS) in lodash',
115+
advisory_url: 'https://github.com/advisories/GHSA-29mw-wpgm-hmr9'
116+
},
117+
{
118+
severity: 'low',
119+
advisory_ghsa_id: 'GHSA-fvqr-27wr-82fm',
120+
advisory_summary: 'Prototype Pollution in lodash',
121+
advisory_url: 'https://github.com/advisories/GHSA-fvqr-27wr-82fm'
122+
}
123+
]
124+
}
125+
68126
test('it properly filters changes by severity', async () => {
69127
const changes = [npmChange, rubyChange]
70128
let result = filterChangesBySeverity('high', changes)
@@ -99,25 +157,61 @@ test('it properly handles undefined advisory IDs', async () => {
99157
test('it properly filters changes with allowed vulnerabilities', async () => {
100158
const changes = [npmChange, rubyChange, noVulnNpmChange]
101159

102-
let result = filterAllowedAdvisories(['notrealGHSAID'], changes)
103-
expect(result).toEqual([npmChange, rubyChange, noVulnNpmChange])
160+
const fakeGHSAChanges = filterAllowedAdvisories(['notrealGHSAID'], changes)
161+
expect(fakeGHSAChanges).toEqual([npmChange, rubyChange, noVulnNpmChange])
162+
})
163+
164+
test('it properly filters only allowed vulnerabilities', async () => {
165+
const changes = [npmChange, rubyChange, noVulnNpmChange]
166+
const oldVulns = [
167+
...npmChange.vulnerabilities,
168+
...rubyChange.vulnerabilities,
169+
...noVulnNpmChange.vulnerabilities
170+
]
104171

105-
result = filterAllowedAdvisories(['first-random_string'], changes)
106-
expect(result).toEqual([rubyChange, noVulnNpmChange])
172+
const vulnerable = filterAllowedAdvisories(['vulnerable-ghsa-id'], changes)
107173

108-
result = filterAllowedAdvisories(
109-
['second-random_string', 'third-random_string'],
110-
changes
174+
const newVulns = vulnerable.map(change => change.vulnerabilities).flat()
175+
176+
expect(newVulns.length).toEqual(oldVulns.length - 1)
177+
expect(newVulns).not.toContainEqual(
178+
expect.objectContaining({advisory_ghsa_id: 'vulnerable-ghsa-id'})
111179
)
112-
expect(result).toEqual([npmChange, noVulnNpmChange])
180+
})
113181

114-
result = filterAllowedAdvisories(
115-
['first-random_string', 'second-random_string', 'third-random_string'],
182+
test('does not drop dependencies when filtering by GHSA', async () => {
183+
const changes = [npmChange, rubyChange, noVulnNpmChange]
184+
const result = filterAllowedAdvisories(
185+
['moderate-ghsa-id', 'low-ghsa-id', 'GHSA-jf85-cpcp-j695'],
116186
changes
117187
)
118-
expect(result).toEqual([noVulnNpmChange])
119188

120-
// if we have a change with multiple vulnerabilities but only one is allowed, we still should not filter out that change
121-
result = filterAllowedAdvisories(['second-random_string'], changes)
122-
expect(result).toEqual([npmChange, rubyChange, noVulnNpmChange])
189+
expect(result.map(change => change.name)).toEqual(
190+
changes.map(change => change.name)
191+
)
192+
})
193+
194+
test('it properly filters multiple GHSAs', async () => {
195+
const allowedGHSAs = ['vulnerable-ghsa-id', 'moderate-ghsa-id', 'low-ghsa-id']
196+
const changes = [npmChange, rubyChange, noVulnNpmChange]
197+
const oldVulns = changes.map(change => change.vulnerabilities).flat()
198+
199+
const result = filterAllowedAdvisories(allowedGHSAs, changes)
200+
201+
const newVulns = result.map(change => change.vulnerabilities).flat()
202+
203+
expect(newVulns.length).toEqual(oldVulns.length - 3)
204+
})
205+
206+
test('it properly filters multiple GHSAs', async () => {
207+
const lodash = filterAllowedAdvisories(
208+
['GHSA-jf85-cpcp-j695'],
209+
[lodashChange]
210+
)[0]
211+
// the filter should have removed a single GHSA from the list
212+
const expected = lodashChange.vulnerabilities.filter(
213+
vuln => vuln.advisory_ghsa_id !== 'GHSA-jf85-cpcp-j695'
214+
)
215+
expect(expected.length).toEqual(lodashChange.vulnerabilities.length - 1)
216+
expect(lodash.vulnerabilities).toEqual(expected)
123217
})

src/filter.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,25 +82,20 @@ export function filterAllowedAdvisories(
8282
return changes
8383
}
8484

85-
const filteredChanges = changes.filter(change => {
85+
const filteredChanges = changes.map(change => {
8686
const noAdvisories =
8787
change.vulnerabilities === undefined ||
8888
change.vulnerabilities.length === 0
8989

9090
if (noAdvisories) {
91-
return true
91+
return change
9292
}
93+
const newChange = {...change}
94+
newChange.vulnerabilities = change.vulnerabilities.filter(
95+
vuln => !ghsas.includes(vuln.advisory_ghsa_id)
96+
)
9397

94-
let allAllowedAdvisories = true
95-
// if there's at least one advisory that is not allowlisted, we will keep the change
96-
for (const vulnerability of change.vulnerabilities) {
97-
if (!ghsas.includes(vulnerability.advisory_ghsa_id)) {
98-
allAllowedAdvisories = false
99-
}
100-
if (!allAllowedAdvisories) {
101-
return true
102-
}
103-
}
98+
return newChange
10499
})
105100

106101
return filteredChanges

0 commit comments

Comments
 (0)