@vltpkg/query 1.0.0-rc.32 → 1.0.0-rc.34

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.
Files changed (215) hide show
  1. package/dist/index.js +10 -6
  2. package/dist/parser.js +2 -1
  3. package/dist/pseudo/hostname.js +3 -1
  4. package/dist/pseudo/malware.d.ts +3 -16
  5. package/dist/pseudo/malware.js +11 -171
  6. package/dist/pseudo/scripts.js +1 -1
  7. package/dist/pseudo/vuln.d.ts +22 -0
  8. package/dist/pseudo/vuln.js +220 -0
  9. package/dist/pseudo.js +3 -3
  10. package/dist/types.d.ts +3 -2
  11. package/package.json +19 -10
  12. package/skills/dss-query/REFERENCE.md +123 -0
  13. package/skills/dss-query/SKILL.md +190 -0
  14. package/skills/dss-query/evals/README.md +79 -0
  15. package/skills/dss-query/evals/evals.json +89 -0
  16. package/skills/dss-query/evals/grade.mjs +480 -0
  17. package/src/attribute.ts +186 -0
  18. package/src/combinator.ts +132 -0
  19. package/src/id.ts +46 -0
  20. package/src/index.ts +620 -0
  21. package/src/parser.ts +122 -0
  22. package/src/pseudo/abandoned.ts +9 -0
  23. package/src/pseudo/attr.ts +92 -0
  24. package/src/pseudo/built.ts +19 -0
  25. package/src/pseudo/confused.ts +29 -0
  26. package/src/pseudo/cve.ts +93 -0
  27. package/src/pseudo/cwe.ts +97 -0
  28. package/src/pseudo/debug.ts +9 -0
  29. package/src/pseudo/deprecated.ts +9 -0
  30. package/src/pseudo/dev.ts +18 -0
  31. package/src/pseudo/diff.ts +90 -0
  32. package/src/pseudo/dist.ts +144 -0
  33. package/src/pseudo/dynamic.ts +9 -0
  34. package/src/pseudo/empty.ts +15 -0
  35. package/src/pseudo/entropic.ts +9 -0
  36. package/src/pseudo/env.ts +6 -0
  37. package/src/pseudo/eval.ts +9 -0
  38. package/src/pseudo/fs.ts +9 -0
  39. package/src/pseudo/helpers.ts +106 -0
  40. package/src/pseudo/host.ts +111 -0
  41. package/src/pseudo/hostname.ts +156 -0
  42. package/src/pseudo/license.ts +134 -0
  43. package/src/pseudo/link.ts +30 -0
  44. package/src/pseudo/malware.ts +41 -0
  45. package/src/pseudo/minified.ts +9 -0
  46. package/src/pseudo/missing.ts +16 -0
  47. package/src/pseudo/native.ts +9 -0
  48. package/src/pseudo/network.ts +9 -0
  49. package/src/pseudo/obfuscated.ts +9 -0
  50. package/src/pseudo/optional.ts +18 -0
  51. package/src/pseudo/outdated.ts +305 -0
  52. package/src/pseudo/overridden.ts +20 -0
  53. package/src/pseudo/path.ts +146 -0
  54. package/src/pseudo/peer.ts +18 -0
  55. package/src/pseudo/prerelease.ts +47 -0
  56. package/src/pseudo/private.ts +19 -0
  57. package/src/pseudo/prod.ts +18 -0
  58. package/src/pseudo/published.ts +248 -0
  59. package/src/pseudo/registry.ts +29 -0
  60. package/src/pseudo/root.ts +19 -0
  61. package/src/pseudo/scanned.ts +20 -0
  62. package/src/pseudo/score.ts +186 -0
  63. package/src/pseudo/scripts.ts +54 -0
  64. package/src/pseudo/semver.ts +320 -0
  65. package/src/pseudo/severity.ts +231 -0
  66. package/src/pseudo/shell.ts +9 -0
  67. package/src/pseudo/shrinkwrap.ts +9 -0
  68. package/src/pseudo/spec.ts +131 -0
  69. package/src/pseudo/squat.ts +225 -0
  70. package/src/pseudo/suspicious.ts +9 -0
  71. package/src/pseudo/tracker.ts +9 -0
  72. package/src/pseudo/trivial.ts +9 -0
  73. package/src/pseudo/type.ts +26 -0
  74. package/src/pseudo/undesirable.ts +9 -0
  75. package/src/pseudo/unknown.ts +9 -0
  76. package/src/pseudo/unmaintained.ts +9 -0
  77. package/src/pseudo/unpopular.ts +9 -0
  78. package/src/pseudo/unstable.ts +9 -0
  79. package/src/pseudo/vuln.ts +273 -0
  80. package/src/pseudo/workspace.ts +22 -0
  81. package/src/pseudo.ts +413 -0
  82. package/src/types.ts +165 -0
  83. package/tap-snapshots/test/attribute.ts.test.cjs +304 -0
  84. package/tap-snapshots/test/combinator.ts.test.cjs +333 -0
  85. package/tap-snapshots/test/id.ts.test.cjs +118 -0
  86. package/tap-snapshots/test/parser.ts.test.cjs +344 -0
  87. package/tap-snapshots/test/pseudo/abandoned.ts.test.cjs +18 -0
  88. package/tap-snapshots/test/pseudo/attr.ts.test.cjs +161 -0
  89. package/tap-snapshots/test/pseudo/built.ts.test.cjs +70 -0
  90. package/tap-snapshots/test/pseudo/confused.ts.test.cjs +32 -0
  91. package/tap-snapshots/test/pseudo/cve.ts.test.cjs +49 -0
  92. package/tap-snapshots/test/pseudo/cwe.ts.test.cjs +49 -0
  93. package/tap-snapshots/test/pseudo/debug.ts.test.cjs +18 -0
  94. package/tap-snapshots/test/pseudo/deprecated.ts.test.cjs +18 -0
  95. package/tap-snapshots/test/pseudo/dist.ts.test.cjs +30 -0
  96. package/tap-snapshots/test/pseudo/dynamic.ts.test.cjs +18 -0
  97. package/tap-snapshots/test/pseudo/empty.ts.test.cjs +50 -0
  98. package/tap-snapshots/test/pseudo/entropic.ts.test.cjs +18 -0
  99. package/tap-snapshots/test/pseudo/env.ts.test.cjs +18 -0
  100. package/tap-snapshots/test/pseudo/eval.ts.test.cjs +18 -0
  101. package/tap-snapshots/test/pseudo/fs.ts.test.cjs +18 -0
  102. package/tap-snapshots/test/pseudo/helpers.ts.test.cjs +18 -0
  103. package/tap-snapshots/test/pseudo/hostname.ts.test.cjs +148 -0
  104. package/tap-snapshots/test/pseudo/license.ts.test.cjs +41 -0
  105. package/tap-snapshots/test/pseudo/link.ts.test.cjs +36 -0
  106. package/tap-snapshots/test/pseudo/malware.ts.test.cjs +22 -0
  107. package/tap-snapshots/test/pseudo/minified.ts.test.cjs +18 -0
  108. package/tap-snapshots/test/pseudo/missing.ts.test.cjs +30 -0
  109. package/tap-snapshots/test/pseudo/native.ts.test.cjs +18 -0
  110. package/tap-snapshots/test/pseudo/network.ts.test.cjs +18 -0
  111. package/tap-snapshots/test/pseudo/obfuscated.ts.test.cjs +18 -0
  112. package/tap-snapshots/test/pseudo/outdated.ts.test.cjs +130 -0
  113. package/tap-snapshots/test/pseudo/overridden.ts.test.cjs +75 -0
  114. package/tap-snapshots/test/pseudo/prerelease.ts.test.cjs +101 -0
  115. package/tap-snapshots/test/pseudo/private.ts.test.cjs +38 -0
  116. package/tap-snapshots/test/pseudo/published.ts.test.cjs +171 -0
  117. package/tap-snapshots/test/pseudo/registry.ts.test.cjs +68 -0
  118. package/tap-snapshots/test/pseudo/root.ts.test.cjs +24 -0
  119. package/tap-snapshots/test/pseudo/scanned.ts.test.cjs +18 -0
  120. package/tap-snapshots/test/pseudo/score.ts.test.cjs +239 -0
  121. package/tap-snapshots/test/pseudo/scripts.ts.test.cjs +61 -0
  122. package/tap-snapshots/test/pseudo/semver.ts.test.cjs +575 -0
  123. package/tap-snapshots/test/pseudo/severity.ts.test.cjs +95 -0
  124. package/tap-snapshots/test/pseudo/shell.ts.test.cjs +18 -0
  125. package/tap-snapshots/test/pseudo/shrinkwrap.ts.test.cjs +18 -0
  126. package/tap-snapshots/test/pseudo/spec.ts.test.cjs +148 -0
  127. package/tap-snapshots/test/pseudo/squat.ts.test.cjs +129 -0
  128. package/tap-snapshots/test/pseudo/suspicious.ts.test.cjs +18 -0
  129. package/tap-snapshots/test/pseudo/tracker.ts.test.cjs +18 -0
  130. package/tap-snapshots/test/pseudo/trivial.ts.test.cjs +18 -0
  131. package/tap-snapshots/test/pseudo/type.ts.test.cjs +51 -0
  132. package/tap-snapshots/test/pseudo/undesirable.ts.test.cjs +18 -0
  133. package/tap-snapshots/test/pseudo/unknown.ts.test.cjs +18 -0
  134. package/tap-snapshots/test/pseudo/unmaintained.ts.test.cjs +18 -0
  135. package/tap-snapshots/test/pseudo/unpopular.ts.test.cjs +18 -0
  136. package/tap-snapshots/test/pseudo/unstable.ts.test.cjs +18 -0
  137. package/tap-snapshots/test/pseudo/vuln.ts.test.cjs +208 -0
  138. package/tap-snapshots/test/pseudo/vulnerable.ts.test.cjs +20 -0
  139. package/tap-snapshots/test/pseudo/workspace.ts.test.cjs +19 -0
  140. package/tap-snapshots/test/pseudo.ts.test.cjs +1247 -0
  141. package/test/attribute.ts +266 -0
  142. package/test/combinator.ts +168 -0
  143. package/test/fixtures/graph.ts +999 -0
  144. package/test/fixtures/selector.ts +103 -0
  145. package/test/fixtures/types.ts +7 -0
  146. package/test/id.ts +102 -0
  147. package/test/index.ts +1000 -0
  148. package/test/parser.ts +117 -0
  149. package/test/pseudo/abandoned.ts +98 -0
  150. package/test/pseudo/attr.ts +298 -0
  151. package/test/pseudo/built.ts +214 -0
  152. package/test/pseudo/confused.ts +160 -0
  153. package/test/pseudo/cve.ts +249 -0
  154. package/test/pseudo/cwe.ts +255 -0
  155. package/test/pseudo/debug.ts +98 -0
  156. package/test/pseudo/deprecated.ts +98 -0
  157. package/test/pseudo/dev.ts +61 -0
  158. package/test/pseudo/diff.ts +417 -0
  159. package/test/pseudo/dist.ts +215 -0
  160. package/test/pseudo/dynamic.ts +98 -0
  161. package/test/pseudo/empty.ts +108 -0
  162. package/test/pseudo/entropic.ts +101 -0
  163. package/test/pseudo/env.ts +98 -0
  164. package/test/pseudo/eval.ts +98 -0
  165. package/test/pseudo/fs.ts +98 -0
  166. package/test/pseudo/helpers.ts +370 -0
  167. package/test/pseudo/host-context.ts +276 -0
  168. package/test/pseudo/hostname.ts +314 -0
  169. package/test/pseudo/license.ts +265 -0
  170. package/test/pseudo/link.ts +94 -0
  171. package/test/pseudo/malware.ts +184 -0
  172. package/test/pseudo/minified.ts +98 -0
  173. package/test/pseudo/missing.ts +111 -0
  174. package/test/pseudo/native.ts +98 -0
  175. package/test/pseudo/network.ts +98 -0
  176. package/test/pseudo/obfuscated.ts +98 -0
  177. package/test/pseudo/optional.ts +73 -0
  178. package/test/pseudo/outdated.ts +331 -0
  179. package/test/pseudo/overridden.ts +317 -0
  180. package/test/pseudo/path.ts +680 -0
  181. package/test/pseudo/peer.ts +101 -0
  182. package/test/pseudo/prerelease.ts +279 -0
  183. package/test/pseudo/private.ts +108 -0
  184. package/test/pseudo/prod.ts +61 -0
  185. package/test/pseudo/published.ts +557 -0
  186. package/test/pseudo/registry.ts +122 -0
  187. package/test/pseudo/root.ts +66 -0
  188. package/test/pseudo/scanned.ts +78 -0
  189. package/test/pseudo/score.ts +591 -0
  190. package/test/pseudo/scripts.ts +294 -0
  191. package/test/pseudo/semver.ts +822 -0
  192. package/test/pseudo/severity.ts +310 -0
  193. package/test/pseudo/shell.ts +98 -0
  194. package/test/pseudo/shrinkwrap.ts +98 -0
  195. package/test/pseudo/spec.ts +525 -0
  196. package/test/pseudo/squat.ts +565 -0
  197. package/test/pseudo/suspicious.ts +98 -0
  198. package/test/pseudo/tracker.ts +98 -0
  199. package/test/pseudo/trivial.ts +98 -0
  200. package/test/pseudo/type.ts +105 -0
  201. package/test/pseudo/undesirable.ts +98 -0
  202. package/test/pseudo/unknown.ts +98 -0
  203. package/test/pseudo/unmaintained.ts +98 -0
  204. package/test/pseudo/unpopular.ts +98 -0
  205. package/test/pseudo/unstable.ts +98 -0
  206. package/test/pseudo/vuln.ts +625 -0
  207. package/test/pseudo/vulnerable.ts +137 -0
  208. package/test/pseudo/workspace.ts +111 -0
  209. package/test/pseudo.ts +558 -0
  210. package/tsconfig.json +3 -0
  211. package/tsconfig.publish.json +19 -0
  212. package/tsconfig.publish.tsbuildinfo +1 -0
  213. package/typedoc.mjs +2 -0
  214. package/dist/pseudo/vulnerable.d.ts +0 -8
  215. package/dist/pseudo/vulnerable.js +0 -17
package/src/parser.ts ADDED
@@ -0,0 +1,122 @@
1
+ import postcssSelectorParser from 'postcss-selector-parser'
2
+ import {
3
+ asSelectorNode,
4
+ isCombinatorNode,
5
+ isPseudoNode,
6
+ isTagNode,
7
+ } from '@vltpkg/dss-parser'
8
+ import type { Pseudo, Root } from 'postcss-selector-parser'
9
+ import type { PostcssNode } from '@vltpkg/dss-parser'
10
+
11
+ /**
12
+ * Escapes forward slashes in specific patterns matching @scoped/name paths
13
+ * This will allow usage of unescaped forward slashes necessary for scoped
14
+ * package names in the id selector.
15
+ */
16
+ export const escapeScopedNamesSlashes = (query: string): string =>
17
+ query.replace(
18
+ /(#@(\w|-|\.)+)\//gm,
19
+ (_, scope: string) => `${scope}\\/`,
20
+ )
21
+
22
+ export const escapeDots = (query: string): string =>
23
+ query.replaceAll('.', '\\.')
24
+
25
+ export const unescapeDots = (query: string): string =>
26
+ query.replaceAll('\\.', '.')
27
+
28
+ const pseudoCleanUpNeeded = new Set([
29
+ ':published',
30
+ ':score',
31
+ ':severity',
32
+ ':sev',
33
+ ':squat',
34
+ ':semver',
35
+ ':v',
36
+ ':vuln',
37
+ ':vulnerable',
38
+ ':path',
39
+ ])
40
+
41
+ const hasParamsToEscape = (node: Pseudo) =>
42
+ pseudoCleanUpNeeded.has(node.value)
43
+
44
+ /**
45
+ * Parses a CSS selector string into an AST
46
+ * Handles escaping of forward slashes in specific patterns
47
+ */
48
+ export const parse = (query: string): Root => {
49
+ const escapedQuery = escapeDots(escapeScopedNamesSlashes(query))
50
+ const transformAst = (root: Root) => {
51
+ root.walk((node: PostcssNode) => {
52
+ // clean up the escaped dots
53
+ if (node.value && typeof node.value === 'string') {
54
+ node.value = unescapeDots(node.value)
55
+ }
56
+ if (isPseudoNode(node) && hasParamsToEscape(node)) {
57
+ // these are pseudo nodes that should only take strings as
58
+ // parameters, so in this preparse step we clean up anything
59
+ // that was recognized as a postcss node and transform that
60
+ // into something that can be most likely parsed as a string
61
+ for (const n of node.nodes) {
62
+ // the parameters have a selector node that wraps them up
63
+ const selector = asSelectorNode(n)
64
+ selector.nodes.forEach((currentNode, index, arr) => {
65
+ // get the next node, we'll update it later
66
+ const nextNode = arr[index + 1]
67
+ // if the current node is a combinator node, we'll need to
68
+ // escape it, we do so by removing the node entirely and
69
+ // updating the contents of the next node with its value
70
+ if (
71
+ isCombinatorNode(currentNode) &&
72
+ isTagNode(nextNode)
73
+ ) {
74
+ nextNode.value = `${currentNode.spaces.before}${currentNode.value}${currentNode.spaces.after}${nextNode.value}`
75
+ // make sure to also update the source position
76
+ // references, those are used by the syntax highlighter
77
+ if (
78
+ nextNode.source?.start?.line &&
79
+ currentNode.source?.start?.line
80
+ ) {
81
+ nextNode.source.start.line =
82
+ currentNode.source.start.line
83
+ }
84
+ if (
85
+ nextNode.source?.start?.column &&
86
+ currentNode.source?.start?.column
87
+ ) {
88
+ nextNode.source.start.column =
89
+ currentNode.source.start.column
90
+ }
91
+ // removes the current node from the selector node
92
+ arr.splice(index, 1)
93
+ }
94
+ })
95
+ // after removing combinator nodes, if we end up with multiple
96
+ // tags in the selector node, we need to smush them together
97
+ selector.nodes.reduce((acc, currentNode) => {
98
+ if (currentNode === acc) return acc
99
+ acc.value = `${acc.value}${currentNode.spaces.before}${currentNode.value}${currentNode.spaces.after}`
100
+ // make sure to also update the source position refs
101
+ if (
102
+ currentNode.source?.end?.line &&
103
+ acc.source?.end?.line
104
+ ) {
105
+ acc.source.end.line = currentNode.source.end.line
106
+ }
107
+ if (
108
+ currentNode.source?.end?.column &&
109
+ acc.source?.end?.column
110
+ ) {
111
+ acc.source.end.column = currentNode.source.end.column
112
+ }
113
+ return acc
114
+ }, selector.first)
115
+ // the selector wrapper node should have a single node
116
+ selector.nodes.length = 1
117
+ }
118
+ }
119
+ })
120
+ }
121
+ return postcssSelectorParser(transformAst).astSync(escapedQuery)
122
+ }
@@ -0,0 +1,9 @@
1
+ import { createSecuritySelectorFilter } from './helpers.ts'
2
+
3
+ /**
4
+ * Filters out any node that does not have a **missingAuthor** report alert.
5
+ */
6
+ export const abandoned = createSecuritySelectorFilter(
7
+ 'abandoned',
8
+ 'missingAuthor',
9
+ )
@@ -0,0 +1,92 @@
1
+ import { error } from '@vltpkg/error-cause'
2
+ import {
3
+ asAttributeNode,
4
+ asPostcssNodeWithChildren,
5
+ asStringNode,
6
+ asTagNode,
7
+ isStringNode,
8
+ } from '@vltpkg/dss-parser'
9
+ import {
10
+ attributeSelectorsMap,
11
+ filterAttributes,
12
+ } from '../attribute.ts'
13
+ import { removeQuotes } from './helpers.ts'
14
+ import type { ParserState } from '../types.ts'
15
+ import type { PostcssNode } from '@vltpkg/dss-parser'
16
+
17
+ export type AttrInternals = {
18
+ attribute: string
19
+ insensitive: boolean
20
+ operator?: string
21
+ value?: string
22
+ properties: string[]
23
+ }
24
+
25
+ /**
26
+ * Parses the internal / nested selectors of a `:attr` selector.
27
+ */
28
+ export const parseInternals = (
29
+ nodes: PostcssNode[],
30
+ ): AttrInternals => {
31
+ // the last part is the attribute selector
32
+ const attributeSelector = asAttributeNode(
33
+ asPostcssNodeWithChildren(nodes.pop()).nodes[0],
34
+ )
35
+ // all preppending selectors are naming nested properties
36
+ const properties: string[] = []
37
+ for (const selector of nodes) {
38
+ const selectorNode = asPostcssNodeWithChildren(selector).nodes[0]
39
+ // Handle both quoted string and tag nodes
40
+ if (isStringNode(selectorNode)) {
41
+ properties.push(removeQuotes(asStringNode(selectorNode).value))
42
+ } else {
43
+ properties.push(asTagNode(selectorNode).value)
44
+ }
45
+ }
46
+ // include the attribute selector as the last part of the property lookup
47
+ properties.push(attributeSelector.attribute)
48
+
49
+ return {
50
+ attribute: attributeSelector.attribute,
51
+ insensitive: attributeSelector.insensitive || false,
52
+ operator: attributeSelector.operator,
53
+ value: attributeSelector.value,
54
+ properties,
55
+ }
56
+ }
57
+
58
+ /**
59
+ * :attr Pseudo-Selector, allows for retrieving nodes based on nested
60
+ * properties of the `package.json` metadata.
61
+ */
62
+ export const attr = async (state: ParserState) => {
63
+ // Parses and retrieves the values for the nested selectors
64
+ let internals
65
+ try {
66
+ internals = parseInternals(
67
+ asPostcssNodeWithChildren(state.current).nodes,
68
+ )
69
+ } catch (err) {
70
+ throw error('Failed to parse :attr selector', {
71
+ cause: err,
72
+ })
73
+ }
74
+
75
+ // reuses the attribute selector logic to filter the nodes
76
+ const comparator =
77
+ internals.operator ?
78
+ attributeSelectorsMap.get(internals.operator)
79
+ : undefined
80
+ const value = internals.value || ''
81
+ const propertyName = internals.attribute
82
+ const insensitive = internals.insensitive
83
+ const prefixProperties = internals.properties
84
+ return filterAttributes(
85
+ state,
86
+ comparator,
87
+ value,
88
+ propertyName,
89
+ insensitive,
90
+ prefixProperties,
91
+ )
92
+ }
@@ -0,0 +1,19 @@
1
+ import type { ParserState } from '../types.ts'
2
+ import { removeNode, removeDanglingEdges } from './helpers.ts'
3
+
4
+ /**
5
+ * :built Pseudo-Selector will only match packages that have
6
+ * a `buildState` property set to 'built', indicating they have
7
+ * been successfully built during the reify process.
8
+ */
9
+ export const built = async (state: ParserState) => {
10
+ for (const node of state.partial.nodes) {
11
+ if (node.buildState !== 'built') {
12
+ removeNode(state, node)
13
+ }
14
+ }
15
+
16
+ removeDanglingEdges(state)
17
+
18
+ return state
19
+ }
@@ -0,0 +1,29 @@
1
+ import type { ParserState } from '../types.js'
2
+ import {
3
+ assertSecurityArchive,
4
+ removeDanglingEdges,
5
+ removeNode,
6
+ } from './helpers.ts'
7
+
8
+ /**
9
+ * Filters out any node that does not have a **manifestConfusion** report alert.
10
+ * Also includes any node that has been marked as **confused**.
11
+ */
12
+ export const confused = async (state: ParserState) => {
13
+ assertSecurityArchive(state, 'confused')
14
+
15
+ for (const node of state.partial.nodes) {
16
+ const report = state.securityArchive.get(node.id)
17
+ const exclude =
18
+ !node.confused &&
19
+ !report?.alerts.some(
20
+ alert => alert.type === 'manifestConfusion',
21
+ )
22
+ if (exclude) {
23
+ removeNode(state, node)
24
+ }
25
+ }
26
+
27
+ removeDanglingEdges(state)
28
+ return state
29
+ }
@@ -0,0 +1,93 @@
1
+ import { error } from '@vltpkg/error-cause'
2
+ import {
3
+ asPostcssNodeWithChildren,
4
+ asStringNode,
5
+ asTagNode,
6
+ isStringNode,
7
+ isTagNode,
8
+ } from '@vltpkg/dss-parser'
9
+ import {
10
+ assertSecurityArchive,
11
+ removeDanglingEdges,
12
+ removeNode,
13
+ removeQuotes,
14
+ } from './helpers.ts'
15
+ import type { ParserState } from '../types.ts'
16
+ import type { PostcssNode } from '@vltpkg/dss-parser'
17
+
18
+ export type CveInternals = {
19
+ cveId: string | undefined
20
+ }
21
+
22
+ export const parseInternals = (
23
+ nodes: PostcssNode[],
24
+ ): CveInternals => {
25
+ if (!nodes[0]) {
26
+ return { cveId: undefined }
27
+ }
28
+
29
+ const selectorNode = asPostcssNodeWithChildren(nodes[0])
30
+ if (!selectorNode.nodes[0]) {
31
+ return { cveId: undefined }
32
+ }
33
+
34
+ let cveId = ''
35
+
36
+ if (isStringNode(selectorNode.nodes[0])) {
37
+ cveId = removeQuotes(asStringNode(selectorNode.nodes[0]).value)
38
+ } else if (isTagNode(selectorNode.nodes[0])) {
39
+ cveId = asTagNode(selectorNode.nodes[0]).value
40
+ }
41
+
42
+ /* c8 ignore start - unreachable via normal parser */
43
+ if (!cveId) {
44
+ throw error('Expected a CVE ID', {
45
+ found: selectorNode.nodes[0],
46
+ })
47
+ }
48
+ /* c8 ignore stop */
49
+
50
+ return { cveId }
51
+ }
52
+
53
+ /**
54
+ * Filters out any node that does not have a CVE alert.
55
+ *
56
+ * Usage:
57
+ * - :cve - matches any package with at least one CVE alert
58
+ * - :cve(CVE-2023-1234) - matches only the specific CVE ID
59
+ */
60
+ export const cve = async (state: ParserState) => {
61
+ assertSecurityArchive(state, 'cve')
62
+
63
+ let internals
64
+ try {
65
+ internals = parseInternals(
66
+ asPostcssNodeWithChildren(state.current).nodes,
67
+ )
68
+ } catch (err) /* c8 ignore start */ {
69
+ throw error('Failed to parse :cve selector', { cause: err })
70
+ } /* c8 ignore stop */
71
+
72
+ const { cveId } = internals
73
+ for (const node of state.partial.nodes) {
74
+ const report = state.securityArchive.get(node.id)
75
+ let exclude: boolean
76
+ if (cveId === undefined) {
77
+ exclude = !report?.alerts.some(alert => alert.props?.cveId)
78
+ } else {
79
+ exclude = !report?.alerts.some(
80
+ alert =>
81
+ alert.props?.cveId?.trim().toLowerCase() ===
82
+ cveId.trim().toLowerCase(),
83
+ )
84
+ }
85
+ if (exclude) {
86
+ removeNode(state, node)
87
+ }
88
+ }
89
+
90
+ removeDanglingEdges(state)
91
+
92
+ return state
93
+ }
@@ -0,0 +1,97 @@
1
+ import { error } from '@vltpkg/error-cause'
2
+ import {
3
+ asPostcssNodeWithChildren,
4
+ asStringNode,
5
+ asTagNode,
6
+ isStringNode,
7
+ isTagNode,
8
+ } from '@vltpkg/dss-parser'
9
+ import {
10
+ assertSecurityArchive,
11
+ removeDanglingEdges,
12
+ removeNode,
13
+ removeQuotes,
14
+ } from './helpers.ts'
15
+ import type { ParserState } from '../types.ts'
16
+ import type { PostcssNode } from '@vltpkg/dss-parser'
17
+
18
+ export type CweInternals = {
19
+ cweId: string | undefined
20
+ }
21
+
22
+ export const parseInternals = (
23
+ nodes: PostcssNode[],
24
+ ): CweInternals => {
25
+ if (!nodes[0]) {
26
+ return { cweId: undefined }
27
+ }
28
+
29
+ const selectorNode = asPostcssNodeWithChildren(nodes[0])
30
+ if (!selectorNode.nodes[0]) {
31
+ return { cweId: undefined }
32
+ }
33
+
34
+ let cweId = ''
35
+
36
+ if (isStringNode(selectorNode.nodes[0])) {
37
+ cweId = removeQuotes(asStringNode(selectorNode.nodes[0]).value)
38
+ } else if (isTagNode(selectorNode.nodes[0])) {
39
+ cweId = asTagNode(selectorNode.nodes[0]).value
40
+ }
41
+
42
+ /* c8 ignore start - unreachable via normal parser */
43
+ if (!cweId) {
44
+ throw error('Expected a CWE ID', {
45
+ found: selectorNode.nodes[0],
46
+ })
47
+ }
48
+ /* c8 ignore stop */
49
+
50
+ return { cweId }
51
+ }
52
+
53
+ /**
54
+ * Filters out any node that does not have a CWE alert.
55
+ *
56
+ * Usage:
57
+ * - :cwe - matches any package with at least one CWE alert
58
+ * - :cwe(CWE-79) - matches only the specific CWE ID
59
+ */
60
+ export const cwe = async (state: ParserState) => {
61
+ assertSecurityArchive(state, 'cwe')
62
+
63
+ let internals
64
+ try {
65
+ internals = parseInternals(
66
+ asPostcssNodeWithChildren(state.current).nodes,
67
+ )
68
+ } catch (err) /* c8 ignore start */ {
69
+ throw error('Failed to parse :cwe selector', { cause: err })
70
+ } /* c8 ignore stop */
71
+
72
+ const { cweId } = internals
73
+ for (const node of state.partial.nodes) {
74
+ const report = state.securityArchive.get(node.id)
75
+ let exclude: boolean
76
+ if (cweId === undefined) {
77
+ exclude = !report?.alerts.some(
78
+ alert => alert.props?.cwes && alert.props.cwes.length > 0,
79
+ )
80
+ } else {
81
+ exclude = !report?.alerts.some(alert =>
82
+ alert.props?.cwes?.some(
83
+ cwe =>
84
+ cwe.id.trim().toLowerCase() ===
85
+ cweId.trim().toLowerCase(),
86
+ ),
87
+ )
88
+ }
89
+ if (exclude) {
90
+ removeNode(state, node)
91
+ }
92
+ }
93
+
94
+ removeDanglingEdges(state)
95
+
96
+ return state
97
+ }
@@ -0,0 +1,9 @@
1
+ import { createSecuritySelectorFilter } from './helpers.ts'
2
+
3
+ /**
4
+ * Filters out any node that does not have a **debugAccess** report alert.
5
+ */
6
+ export const debug = createSecuritySelectorFilter(
7
+ 'debug',
8
+ 'debugAccess',
9
+ )
@@ -0,0 +1,9 @@
1
+ import { createSecuritySelectorFilter } from './helpers.ts'
2
+
3
+ /**
4
+ * Filters out any node that does not have a **deprecated** report alert.
5
+ */
6
+ export const deprecated = createSecuritySelectorFilter(
7
+ 'deprecated',
8
+ 'deprecated',
9
+ )
@@ -0,0 +1,18 @@
1
+ import type { ParserState } from '../types.ts'
2
+ import { removeEdge, removeUnlinkedNodes } from './helpers.ts'
3
+
4
+ /**
5
+ * :dev Pseudo-Selector will only match devDependencies.
6
+ */
7
+ export const dev = async (state: ParserState) => {
8
+ // filter edges that don't have type 'dev'
9
+ for (const edge of state.partial.edges) {
10
+ if (edge.type !== 'dev') {
11
+ removeEdge(state, edge)
12
+ }
13
+ }
14
+
15
+ removeUnlinkedNodes(state)
16
+
17
+ return state
18
+ }
@@ -0,0 +1,90 @@
1
+ import { error } from '@vltpkg/error-cause'
2
+ import { asPostcssNodeWithChildren } from '@vltpkg/dss-parser'
3
+ import { removeDanglingEdges, removeNode } from './helpers.ts'
4
+ import { parseInternals } from './spec.ts'
5
+ import type { ParserState } from '../types.ts'
6
+
7
+ /**
8
+ * Given a set of changed file paths and a package location
9
+ * (relative to the project root), returns true if any changed
10
+ * file belongs to that package.
11
+ */
12
+ export const packageHasChanges = (
13
+ changedFiles: Set<string>,
14
+ packageLocation: string,
15
+ ): boolean => {
16
+ // Normalize the package location — remove leading './'
17
+ const normalized =
18
+ packageLocation.startsWith('./') ?
19
+ packageLocation.slice(2)
20
+ : packageLocation
21
+
22
+ // Root package — check if any file at root level changed
23
+ // (not inside a subdirectory that is another package)
24
+ if (!normalized || normalized === '.') {
25
+ // Any changed file means the root package changed
26
+ return changedFiles.size > 0
27
+ }
28
+
29
+ // Check if any changed file starts with the package path
30
+ for (const file of changedFiles) {
31
+ if (file === normalized || file.startsWith(normalized + '/')) {
32
+ return true
33
+ }
34
+ }
35
+ return false
36
+ }
37
+
38
+ /**
39
+ * :diff(<commitish>) Pseudo-Selector, matches only nodes
40
+ * whose files have changed between the current state and
41
+ * the specified commitish reference.
42
+ *
43
+ * Requires a `diffFiles` provider to be set on the
44
+ * {@link ParserState}. The provider is a callback that
45
+ * takes a commitish string and returns a Set of changed
46
+ * file paths relative to the project root.
47
+ *
48
+ * When called with no argument, defaults to HEAD (uncommitted changes).
49
+ *
50
+ * Examples:
51
+ * - :diff() — packages with uncommitted changes (defaults to HEAD)
52
+ * - :diff(main) — packages changed since main branch
53
+ * - :diff(HEAD~1) — packages changed since last commit
54
+ * - :diff("v1.0.0") — packages changed since tag v1.0.0
55
+ */
56
+ export const diff = async (state: ParserState) => {
57
+ const currentNodes = asPostcssNodeWithChildren(state.current).nodes
58
+ const hasArg =
59
+ currentNodes.length > 0 &&
60
+ asPostcssNodeWithChildren(currentNodes[0]).nodes.length > 0
61
+
62
+ let commitish = 'HEAD'
63
+ if (hasArg) {
64
+ try {
65
+ commitish = parseInternals(currentNodes).specValue
66
+ } catch (err) {
67
+ throw error('Failed to parse :diff selector', {
68
+ cause: err,
69
+ })
70
+ }
71
+ }
72
+
73
+ if (!state.diffFiles) {
74
+ throw error('The :diff() selector requires a diffFiles provider')
75
+ }
76
+
77
+ const changedFiles = state.diffFiles(commitish)
78
+
79
+ for (const node of state.partial.nodes) {
80
+ /* c8 ignore next -- location is always set on real nodes */
81
+ const location = node.location ?? ''
82
+ if (!packageHasChanges(changedFiles, location)) {
83
+ removeNode(state, node)
84
+ }
85
+ }
86
+
87
+ removeDanglingEdges(state)
88
+
89
+ return state
90
+ }