Skip to content

Commit 5541137

Browse files
committed
feat: add :vuln pseudo class
1 parent e0d574d commit 5541137

File tree

3 files changed

+236
-19
lines changed

3 files changed

+236
-19
lines changed

lib/index.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,46 @@ const fixupOutdated = astNode => {
166166
}
167167
}
168168

169+
const fixupVuln = astNode => {
170+
const vulns = []
171+
if (astNode.nodes.length) {
172+
for (const selector of astNode.nodes) {
173+
const vuln = {}
174+
for (const node of selector.nodes) {
175+
if (node.type !== 'attribute') {
176+
throw Object.assign(
177+
new Error(':vuln pseudo-class only accepts attribute matchers or "cwe" tag'),
178+
{ code: 'EQUERYATTR' }
179+
)
180+
}
181+
if (!['severity', 'cwe'].includes(node._attribute)) {
182+
throw Object.assign(
183+
new Error(':vuln pseudo-class only matches "severity" and "cwe" attributes'),
184+
{ code: 'EQUERYATTR' }
185+
)
186+
}
187+
if (!node.operator) {
188+
node.operator = '='
189+
node.value = '*'
190+
}
191+
if (node.operator !== '=') {
192+
throw Object.assign(
193+
new Error(':vuln pseudo-class attribute selector only accepts "=" operator', node),
194+
{ code: 'EQUERYATTR' }
195+
)
196+
}
197+
if (!vuln[node._attribute]) {
198+
vuln[node._attribute] = []
199+
}
200+
vuln[node._attribute].push(node._value)
201+
}
202+
vulns.push(vuln)
203+
}
204+
astNode.vulns = vulns
205+
astNode.nodes.length = 0
206+
}
207+
}
208+
169209
// a few of the supported ast nodes need to be tweaked in order to properly be
170210
// interpreted as proper arborist query selectors, namely semver ranges from
171211
// both ids and :semver pseudo-class selectors need to be translated from what
@@ -192,6 +232,8 @@ const transformAst = selector => {
192232
return fixupTypes(nextAstNode)
193233
case ':outdated':
194234
return fixupOutdated(nextAstNode)
235+
case ':vuln':
236+
return fixupVuln(nextAstNode)
195237
}
196238
})
197239
}

tap-snapshots/test/index.js.test.cjs

Lines changed: 182 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13905,14 +13905,18 @@ exports[`test/index.js TAP queries :vuln([cwe=400],[severity=medium]) > :vuln([c
1390513905
},
1390613906
"type": "pseudo",
1390713907
"value": ":vuln",
13908-
"vulns": Object {
13909-
"cwe": Array [
13910-
"400",
13911-
],
13912-
"severity": Array [
13913-
"medium",
13914-
],
13915-
},
13908+
"vulns": Array [
13909+
Object {
13910+
"cwe": Array [
13911+
"400",
13912+
],
13913+
},
13914+
Object {
13915+
"severity": Array [
13916+
"medium",
13917+
],
13918+
},
13919+
],
1391613920
},
1391713921
],
1391813922
"parent": <*ref_2>,
@@ -13951,6 +13955,81 @@ exports[`test/index.js TAP queries :vuln([cwe=400],[severity=medium]) > :vuln([c
1395113955
}
1395213956
`
1395313957

13958+
exports[`test/index.js TAP queries :vuln([cwe]) > :vuln([cwe]) 1`] = `
13959+
&ref_2 Root {
13960+
"_error": Function (message, errorOptions),
13961+
"indexes": Object {},
13962+
"lastEach": 1,
13963+
"nodes": Array [
13964+
&ref_1 Selector {
13965+
"indexes": Object {},
13966+
"lastEach": 1,
13967+
"nodes": Array [
13968+
Pseudo {
13969+
"nodes": Array [],
13970+
"parent": <*ref_1>,
13971+
"source": Object {
13972+
"end": Object {
13973+
"column": 12,
13974+
"line": 1,
13975+
},
13976+
"start": Object {
13977+
"column": 1,
13978+
"line": 1,
13979+
},
13980+
},
13981+
"sourceIndex": 0,
13982+
"spaces": Object {
13983+
"after": "",
13984+
"before": "",
13985+
},
13986+
"type": "pseudo",
13987+
"value": ":vuln",
13988+
"vulns": Array [
13989+
Object {
13990+
"cwe": Array [
13991+
"*",
13992+
],
13993+
},
13994+
],
13995+
},
13996+
],
13997+
"parent": <*ref_2>,
13998+
"source": Object {
13999+
"end": Object {
14000+
"column": 12,
14001+
"line": 1,
14002+
},
14003+
"start": Object {
14004+
"column": 1,
14005+
"line": 1,
14006+
},
14007+
},
14008+
"spaces": Object {
14009+
"after": "",
14010+
"before": "",
14011+
},
14012+
"type": "selector",
14013+
},
14014+
],
14015+
"source": Object {
14016+
"end": Object {
14017+
"column": 12,
14018+
"line": 1,
14019+
},
14020+
"start": Object {
14021+
"column": 1,
14022+
"line": 1,
14023+
},
14024+
},
14025+
"spaces": Object {
14026+
"after": "",
14027+
"before": "",
14028+
},
14029+
"type": "root",
14030+
}
14031+
`
14032+
1395414033
exports[`test/index.js TAP queries :vuln([severity=high]) > :vuln([severity=high]) 1`] = `
1395514034
&ref_2 Root {
1395614035
"_error": Function (message, errorOptions),
@@ -13981,11 +14060,13 @@ exports[`test/index.js TAP queries :vuln([severity=high]) > :vuln([severity=high
1398114060
},
1398214061
"type": "pseudo",
1398314062
"value": ":vuln",
13984-
"vulns": Object {
13985-
"severity": Array [
13986-
"high",
13987-
],
13988-
},
14063+
"vulns": Array [
14064+
Object {
14065+
"severity": Array [
14066+
"high",
14067+
],
14068+
},
14069+
],
1398914070
},
1399014071
],
1399114072
"parent": <*ref_2>,
@@ -14054,12 +14135,18 @@ exports[`test/index.js TAP queries :vuln([severity=high],[severity=medium]) > :v
1405414135
},
1405514136
"type": "pseudo",
1405614137
"value": ":vuln",
14057-
"vulns": Object {
14058-
"severity": Array [
14059-
"high",
14060-
"medium",
14061-
],
14062-
},
14138+
"vulns": Array [
14139+
Object {
14140+
"severity": Array [
14141+
"high",
14142+
],
14143+
},
14144+
Object {
14145+
"severity": Array [
14146+
"medium",
14147+
],
14148+
},
14149+
],
1406314150
},
1406414151
],
1406514152
"parent": <*ref_2>,
@@ -14098,6 +14185,82 @@ exports[`test/index.js TAP queries :vuln([severity=high],[severity=medium]) > :v
1409814185
}
1409914186
`
1410014187

14188+
exports[`test/index.js TAP queries :vuln([severity=high][severity=medium]) > :vuln([severity=high][severity=medium]) 1`] = `
14189+
&ref_2 Root {
14190+
"_error": Function (message, errorOptions),
14191+
"indexes": Object {},
14192+
"lastEach": 1,
14193+
"nodes": Array [
14194+
&ref_1 Selector {
14195+
"indexes": Object {},
14196+
"lastEach": 1,
14197+
"nodes": Array [
14198+
Pseudo {
14199+
"nodes": Array [],
14200+
"parent": <*ref_1>,
14201+
"source": Object {
14202+
"end": Object {
14203+
"column": 39,
14204+
"line": 1,
14205+
},
14206+
"start": Object {
14207+
"column": 1,
14208+
"line": 1,
14209+
},
14210+
},
14211+
"sourceIndex": 0,
14212+
"spaces": Object {
14213+
"after": "",
14214+
"before": "",
14215+
},
14216+
"type": "pseudo",
14217+
"value": ":vuln",
14218+
"vulns": Array [
14219+
Object {
14220+
"severity": Array [
14221+
"high",
14222+
"medium",
14223+
],
14224+
},
14225+
],
14226+
},
14227+
],
14228+
"parent": <*ref_2>,
14229+
"source": Object {
14230+
"end": Object {
14231+
"column": 39,
14232+
"line": 1,
14233+
},
14234+
"start": Object {
14235+
"column": 1,
14236+
"line": 1,
14237+
},
14238+
},
14239+
"spaces": Object {
14240+
"after": "",
14241+
"before": "",
14242+
},
14243+
"type": "selector",
14244+
},
14245+
],
14246+
"source": Object {
14247+
"end": Object {
14248+
"column": 39,
14249+
"line": 1,
14250+
},
14251+
"start": Object {
14252+
"column": 1,
14253+
"line": 1,
14254+
},
14255+
},
14256+
"spaces": Object {
14257+
"after": "",
14258+
"before": "",
14259+
},
14260+
"type": "root",
14261+
}
14262+
`
14263+
1410114264
exports[`test/index.js TAP queries > #a > > #a 1`] = `
1410214265
&ref_2 Root {
1410314266
"_error": Function (message, errorOptions),

test/index.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
'use strict'
2+
/* eslint-disable max-len */
23

34
const t = require('tap')
45
const { parser } = require('../lib/index.js')
@@ -125,6 +126,14 @@ const checks = [
125126
[':outdated'],
126127
[':outdated(any)'],
127128

129+
// :vuln pseudo
130+
[':vuln'],
131+
[':vuln([cwe])'],
132+
[':vuln([severity=high])'],
133+
[':vuln([severity=high],[severity=medium])'],
134+
[':vuln([severity=high][severity=medium])'],
135+
[':vuln([cwe=400],[severity=medium])'],
136+
128137
// attribute matchers
129138
['[name]'],
130139
['[name=a]'],
@@ -188,6 +197,9 @@ const throws = [
188197
[':attr(foo, bar)', { code: 'EQUERYATTR' }, 'missing attribute matcher on :attr pseudo-class'],
189198
[':semver(14, [version], [version])', { code: 'ESEMVERFUNC' }, 'third :semver param is not a tag or string'],
190199
[':semver([version], [version])', { code: 'ESEMVERVALUE' }, 'should throw when neither of the first :semver params is a static value'],
200+
[':vuln(.prod)', { code: 'EQUERYATTR' }, ':vuln pseudo-class only accepts attribute matchers'],
201+
[':vuln([description=asdf])', { code: 'EQUERYATTR' }, ':vuln pseudo-class only matches "severity" and "cwe" attributes'],
202+
[':vuln([severity~=medium])', { code: 'EQUERYATTR' }, ':vuln pseudo-class severity selector only accepts "=" operator'],
191203
]
192204

193205
t.test('queries', t => {

0 commit comments

Comments
 (0)