@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
@@ -0,0 +1,231 @@
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 SeverityKinds =
19
+ | '0'
20
+ | '1'
21
+ | '2'
22
+ | '3'
23
+ | 'critical'
24
+ | 'high'
25
+ | 'medium'
26
+ | 'low'
27
+ | undefined
28
+
29
+ export type SeverityAlertTypes =
30
+ | 'criticalCVE'
31
+ | 'cve'
32
+ | 'potentialVulnerability'
33
+ | 'mildCVE'
34
+ | undefined
35
+
36
+ export type SeverityComparator = '>' | '<' | '>=' | '<=' | undefined
37
+
38
+ const kindsMap = new Map<SeverityKinds, SeverityAlertTypes>([
39
+ ['critical', 'criticalCVE'],
40
+ ['high', 'cve'],
41
+ ['medium', 'potentialVulnerability'],
42
+ ['low', 'mildCVE'],
43
+ ['0', 'criticalCVE'],
44
+ ['1', 'cve'],
45
+ ['2', 'potentialVulnerability'],
46
+ ['3', 'mildCVE'],
47
+ ])
48
+
49
+ // Map numerical values to their respective kinds for comparison operations
50
+ const kindLevelMap = new Map<SeverityKinds, number>([
51
+ ['critical', 0],
52
+ ['high', 1],
53
+ ['medium', 2],
54
+ ['low', 3],
55
+ ['0', 0],
56
+ ['1', 1],
57
+ ['2', 2],
58
+ ['3', 3],
59
+ ])
60
+
61
+ const kinds = new Set(kindsMap.keys())
62
+
63
+ export const isSeverityKind = (
64
+ value?: string,
65
+ ): value is SeverityKinds => kinds.has(value as SeverityKinds)
66
+
67
+ export const asSeverityKind = (value?: string): SeverityKinds => {
68
+ if (!isSeverityKind(value)) {
69
+ throw error('Expected a valid severity kind', {
70
+ found: value,
71
+ validOptions: Array.from(kinds),
72
+ })
73
+ }
74
+ return value
75
+ }
76
+
77
+ export const parseInternals = (
78
+ nodes: PostcssNode[],
79
+ ): {
80
+ kind: SeverityKinds
81
+ comparator: SeverityComparator
82
+ } => {
83
+ let kind: SeverityKinds
84
+ let comparator: SeverityComparator
85
+
86
+ if (nodes.length === 0) {
87
+ throw error('Missing severity kind parameter')
88
+ }
89
+
90
+ let kindValue = ''
91
+ if (isStringNode(asPostcssNodeWithChildren(nodes[0]).nodes[0])) {
92
+ kindValue = removeQuotes(
93
+ asStringNode(asPostcssNodeWithChildren(nodes[0]).nodes[0])
94
+ .value,
95
+ )
96
+ } else if (
97
+ isTagNode(asPostcssNodeWithChildren(nodes[0]).nodes[0])
98
+ ) {
99
+ kindValue = asTagNode(
100
+ asPostcssNodeWithChildren(nodes[0]).nodes[0],
101
+ ).value
102
+ }
103
+
104
+ // Extract comparator if present
105
+ if (kindValue.startsWith('>=')) {
106
+ comparator = '>='
107
+ kindValue = kindValue.substring(2)
108
+ } else if (kindValue.startsWith('<=')) {
109
+ comparator = '<='
110
+ kindValue = kindValue.substring(2)
111
+ } else if (kindValue.startsWith('>')) {
112
+ comparator = '>'
113
+ kindValue = kindValue.substring(1)
114
+ } else if (kindValue.startsWith('<')) {
115
+ comparator = '<'
116
+ kindValue = kindValue.substring(1)
117
+ }
118
+
119
+ // Parse kind value
120
+ if (kindValue) {
121
+ if (isSeverityKind(kindValue)) {
122
+ kind = kindValue
123
+ } else {
124
+ throw error(
125
+ 'Expected a valid severity kind or number between 0-3',
126
+ {
127
+ found: kindValue,
128
+ },
129
+ )
130
+ }
131
+ }
132
+
133
+ return { kind, comparator }
134
+ }
135
+
136
+ export const severity = async (state: ParserState) => {
137
+ assertSecurityArchive(state, 'severity')
138
+
139
+ let internals
140
+ try {
141
+ internals = parseInternals(
142
+ asPostcssNodeWithChildren(state.current).nodes,
143
+ )
144
+ } catch (err) {
145
+ throw error('Failed to parse :severity selector', { cause: err })
146
+ }
147
+
148
+ const { kind, comparator } = internals
149
+
150
+ for (const node of state.partial.nodes) {
151
+ const report = state.securityArchive.get(node.id)
152
+ // Always exclude nodes that don't have security data or alerts
153
+ if (!report?.alerts || report.alerts.length === 0) {
154
+ removeNode(state, node)
155
+ }
156
+ }
157
+
158
+ for (const node of state.partial.nodes) {
159
+ const report = state.securityArchive.get(node.id)
160
+ let exclude = true
161
+
162
+ if (report) {
163
+ if (comparator) {
164
+ // retrieve the value to compare against
165
+ const kindLevel = kindLevelMap.get(kind)
166
+ // the kindLevel value has already been validated at this point
167
+ // and thus can never return an undefined/falsy value but ts doesn't
168
+ // know about that, so we have the extra check here
169
+ /* c8 ignore next - impossible */
170
+ if (!kindLevel) break
171
+
172
+ // Check each alert to find any that match our comparison criteria
173
+ for (const alert of report.alerts) {
174
+ // Get the numerical value of the alert type
175
+ const alertType = alert.type
176
+
177
+ // retrieve a key to the current alert level to be compared against
178
+ const currentAlertLevelKey = [...kindsMap.entries()].find(
179
+ ([_, alertValue]) => alertValue === alertType,
180
+ )?.[0]
181
+
182
+ // perform the comparison based on the user-provided kindLevel
183
+ if (currentAlertLevelKey) {
184
+ const currentAlertLevel = kindLevelMap.get(
185
+ currentAlertLevelKey,
186
+ )
187
+ /* c8 ignore next - impossible but ts doesn't know */
188
+ if (currentAlertLevel == null) continue
189
+
190
+ switch (comparator) {
191
+ case '>':
192
+ if (currentAlertLevel > kindLevel) {
193
+ exclude = false
194
+ }
195
+ break
196
+ case '<':
197
+ if (currentAlertLevel < kindLevel) {
198
+ exclude = false
199
+ }
200
+ break
201
+ case '>=':
202
+ if (currentAlertLevel >= kindLevel) {
203
+ exclude = false
204
+ }
205
+ break
206
+ case '<=':
207
+ if (currentAlertLevel <= kindLevel) {
208
+ exclude = false
209
+ }
210
+ break
211
+ }
212
+ }
213
+ }
214
+ } else {
215
+ // Original exact match behavior
216
+ const alertName = kindsMap.get(kind)
217
+ exclude = !report.alerts.some(
218
+ alert => alert.type === alertName,
219
+ )
220
+ }
221
+ }
222
+
223
+ if (exclude) {
224
+ removeNode(state, node)
225
+ }
226
+ }
227
+
228
+ removeDanglingEdges(state)
229
+
230
+ return state
231
+ }
@@ -0,0 +1,9 @@
1
+ import { createSecuritySelectorFilter } from './helpers.ts'
2
+
3
+ /**
4
+ * Filters out any node that does not have a **shellAccess** report alert.
5
+ */
6
+ export const shell = createSecuritySelectorFilter(
7
+ 'shell',
8
+ 'shellAccess',
9
+ )
@@ -0,0 +1,9 @@
1
+ import { createSecuritySelectorFilter } from './helpers.ts'
2
+
3
+ /**
4
+ * Filters out any node that does not have a **shrinkwrap** report alert.
5
+ */
6
+ export const shrinkwrap = createSecuritySelectorFilter(
7
+ 'shrinkwrap',
8
+ 'shrinkwrap',
9
+ )
@@ -0,0 +1,131 @@
1
+ import { error } from '@vltpkg/error-cause'
2
+ import { asError } from '@vltpkg/types'
3
+ import {
4
+ asPostcssNodeWithChildren,
5
+ asStringNode,
6
+ asTagNode,
7
+ isTagNode,
8
+ } from '@vltpkg/dss-parser'
9
+ import {
10
+ removeEdge,
11
+ removeUnlinkedNodes,
12
+ removeQuotes,
13
+ } from './helpers.ts'
14
+ import type { ParserState } from '../types.ts'
15
+ import type { PostcssNode } from '@vltpkg/dss-parser'
16
+
17
+ export type SpecInternals = {
18
+ specValue: string
19
+ }
20
+
21
+ export const parseInternals = (
22
+ nodes: PostcssNode[],
23
+ ): SpecInternals => {
24
+ // tries to parse the first param as a string node, otherwise defaults
25
+ // to reading all postcss nodes as just strings, since it just means
26
+ // the value was defined as an unquoted string
27
+ let specValue = ''
28
+
29
+ if (nodes.length === 0) {
30
+ throw new Error('No nodes provided to parseInternals')
31
+ }
32
+
33
+ const firstNode = asPostcssNodeWithChildren(nodes[0])
34
+
35
+ if (firstNode.nodes.length === 0) {
36
+ throw new Error('First node has no child nodes')
37
+ }
38
+
39
+ // Try to parse as a quoted string first (single string node)
40
+ if (firstNode.nodes.length === 1) {
41
+ const targetNode = firstNode.nodes[0]
42
+ try {
43
+ specValue = removeQuotes(asStringNode(targetNode).value)
44
+ return { specValue }
45
+ } catch (err) {
46
+ if (
47
+ asError(err).message === 'Mismatching query node' &&
48
+ isTagNode(targetNode)
49
+ /* c8 ignore start */
50
+ ) {
51
+ // Handle simple unquoted single-token values like "1"
52
+ const tagNode = asTagNode(targetNode)
53
+ specValue = tagNode.value
54
+ return { specValue }
55
+ /* c8 ignore stop */
56
+ } else {
57
+ throw err
58
+ }
59
+ }
60
+ }
61
+
62
+ // Handle unquoted complex values that get parsed into multiple nodes
63
+ // This happens when unquoted values contain dots, like ^1.0.0 which becomes:
64
+ // - Tag: "^1"
65
+ // - ClassName: "0" (from .0)
66
+ // - ClassName: "0" (from .0)
67
+ // We need to reconstruct the original value by concatenating all nodes
68
+ const parts: string[] = []
69
+
70
+ for (const node of firstNode.nodes) {
71
+ switch (node.type) {
72
+ case 'tag':
73
+ parts.push(asTagNode(node).value)
74
+ break
75
+ case 'class':
76
+ // Class nodes represent .className in CSS, so we need to add the dot back
77
+ parts.push('.' + node.value)
78
+ break
79
+ case 'id':
80
+ // ID nodes represent #idName in CSS, so we need to add the hash back
81
+ parts.push('#' + node.value)
82
+ break
83
+ case 'string':
84
+ parts.push(removeQuotes(asStringNode(node).value))
85
+ break
86
+ default:
87
+ // For other node types, try to get the value property or convert to string
88
+ /* c8 ignore next */
89
+ parts.push(node.value ?? String(node))
90
+ break
91
+ }
92
+ }
93
+
94
+ specValue = parts.join('')
95
+ return { specValue }
96
+ }
97
+
98
+ /**
99
+ * :spec Pseudo-Selector, matches edges where edge.spec.bareSpec equals the provided value.
100
+ *
101
+ * Examples:
102
+ * - :spec("*") matches edges with a package specifier equal to "*"
103
+ * - :spec(^1.0.0) matches edges with a package specifier equal to "^1.0.0"
104
+ * - :spec("catalog:") matches edges with a package specifier equal to "catalog:"
105
+ * - :spec("workspace:dev") matches edges with a package specifier equal to "workspace:dev"
106
+ */
107
+ export const spec = async (state: ParserState) => {
108
+ let internals
109
+ try {
110
+ internals = parseInternals(
111
+ asPostcssNodeWithChildren(state.current).nodes,
112
+ )
113
+ } catch (err) {
114
+ throw error('Failed to parse :spec selector', {
115
+ cause: err,
116
+ })
117
+ }
118
+
119
+ const { specValue } = internals
120
+
121
+ for (const edge of state.partial.edges) {
122
+ if (edge.spec.bareSpec !== specValue) {
123
+ removeEdge(state, edge)
124
+ }
125
+ }
126
+
127
+ // Clean up unlinked nodes after removing edges
128
+ removeUnlinkedNodes(state)
129
+
130
+ return state
131
+ }
@@ -0,0 +1,225 @@
1
+ import { error } from '@vltpkg/error-cause'
2
+ import { asError } from '@vltpkg/types'
3
+ import {
4
+ asPostcssNodeWithChildren,
5
+ asStringNode,
6
+ asTagNode,
7
+ isStringNode,
8
+ isTagNode,
9
+ } from '@vltpkg/dss-parser'
10
+ import {
11
+ assertSecurityArchive,
12
+ removeDanglingEdges,
13
+ removeNode,
14
+ removeQuotes,
15
+ } from './helpers.ts'
16
+ import type { ParserState } from '../types.ts'
17
+ import type { PostcssNode } from '@vltpkg/dss-parser'
18
+
19
+ export type SquatKinds = '0' | '2' | 'critical' | 'medium' | undefined
20
+
21
+ export type SquatAlertTypes =
22
+ 'didYouMean' | 'gptDidYouMean' | undefined
23
+
24
+ export type SquatComparator = '>' | '<' | '>=' | '<=' | undefined
25
+
26
+ const kindsMap = new Map<SquatKinds, SquatAlertTypes>([
27
+ ['critical', 'didYouMean'],
28
+ ['medium', 'gptDidYouMean'],
29
+ ['0', 'didYouMean'],
30
+ ['2', 'gptDidYouMean'],
31
+ [undefined, undefined],
32
+ ])
33
+
34
+ // Map numerical values to their respective kinds for comparison operations
35
+ const kindLevelMap = new Map<SquatKinds, number>([
36
+ ['critical', 0],
37
+ ['medium', 2],
38
+ ['0', 0],
39
+ ['2', 2],
40
+ ])
41
+
42
+ const kinds = new Set(kindsMap.keys())
43
+
44
+ export const isSquatKind = (value?: string): value is SquatKinds =>
45
+ kinds.has(value as SquatKinds)
46
+
47
+ export const asSquatKind = (value?: string): SquatKinds => {
48
+ if (!isSquatKind(value)) {
49
+ throw error('Expected a valid squat kind', {
50
+ found: value,
51
+ validOptions: Array.from(kinds),
52
+ })
53
+ }
54
+ return value
55
+ }
56
+
57
+ export const parseInternals = (
58
+ nodes: PostcssNode[],
59
+ ): {
60
+ kind: SquatKinds
61
+ comparator: SquatComparator
62
+ } => {
63
+ let kind: SquatKinds
64
+ let comparator: SquatComparator
65
+
66
+ let kindValue = ''
67
+ if (isStringNode(asPostcssNodeWithChildren(nodes[0]).nodes[0])) {
68
+ kindValue = removeQuotes(
69
+ asStringNode(asPostcssNodeWithChildren(nodes[0]).nodes[0])
70
+ .value,
71
+ )
72
+ } else if (
73
+ isTagNode(asPostcssNodeWithChildren(nodes[0]).nodes[0])
74
+ ) {
75
+ kindValue = asTagNode(
76
+ asPostcssNodeWithChildren(nodes[0]).nodes[0],
77
+ ).value
78
+ }
79
+
80
+ // Extract comparator if present
81
+ if (kindValue.startsWith('>=')) {
82
+ comparator = '>='
83
+ kindValue = kindValue.substring(2)
84
+ } else if (kindValue.startsWith('<=')) {
85
+ comparator = '<='
86
+ kindValue = kindValue.substring(2)
87
+ } else if (kindValue.startsWith('>')) {
88
+ comparator = '>'
89
+ kindValue = kindValue.substring(1)
90
+ } else if (kindValue.startsWith('<')) {
91
+ comparator = '<'
92
+ kindValue = kindValue.substring(1)
93
+ }
94
+
95
+ // Parse kind value
96
+ if (kindValue) {
97
+ if (isSquatKind(kindValue)) {
98
+ kind = kindValue
99
+ } else {
100
+ throw error('Expected a valid squat kind for comparison', {
101
+ found: kindValue,
102
+ validOptions: Array.from(kinds),
103
+ })
104
+ }
105
+ }
106
+
107
+ return { kind, comparator }
108
+ }
109
+
110
+ export const squat = async (state: ParserState) => {
111
+ assertSecurityArchive(state, 'squat')
112
+
113
+ let internals
114
+ try {
115
+ internals = parseInternals(
116
+ asPostcssNodeWithChildren(state.current).nodes,
117
+ )
118
+ } catch (err) {
119
+ if (asError(err).message === 'Expected a query node') {
120
+ // No parameters provided - pseudo state form: match ANY squat level
121
+ internals = { kind: undefined, comparator: undefined }
122
+ } else {
123
+ throw error('Failed to parse :squat selector', { cause: err })
124
+ }
125
+ }
126
+
127
+ const { kind, comparator } = internals
128
+
129
+ // First pass: Remove nodes without security data
130
+ for (const node of state.partial.nodes) {
131
+ const report = state.securityArchive.get(node.id)
132
+ // Always exclude nodes that don't have security data or alerts
133
+ if (!report?.alerts || report.alerts.length === 0) {
134
+ removeNode(state, node)
135
+ }
136
+ }
137
+
138
+ // Second pass: Apply comparison filtering
139
+ for (const node of state.partial.nodes) {
140
+ const report = state.securityArchive.get(node.id)
141
+
142
+ // Skip if report is undefined
143
+ // (should never happen since we filtered above)
144
+ /* c8 ignore next - impossible */
145
+ if (!report) continue
146
+
147
+ // At this point we know report exists and has alerts
148
+ let exclude = true
149
+
150
+ if (kind === undefined && comparator === undefined) {
151
+ // Pseudo state form: match ANY squat level
152
+ exclude = !report.alerts.some(alert =>
153
+ [...kindsMap.values()].includes(
154
+ alert.type as SquatAlertTypes,
155
+ ),
156
+ )
157
+ } else if (comparator) {
158
+ // Get the value to compare against
159
+ const kindLevel = kindLevelMap.get(kind)
160
+ /* c8 ignore next - impossible */
161
+ if (kindLevel === undefined) break
162
+
163
+ // For each alert, check if it matches the comparison criteria
164
+ let matchesComparison = false
165
+ for (const alert of report.alerts) {
166
+ // Get the alert type
167
+ const alertType = alert.type
168
+
169
+ // Find the corresponding kind for this alert type
170
+ const alertLevelKey = [...kindsMap.entries()].find(
171
+ ([_, value]) => value === alertType,
172
+ )?.[0]
173
+
174
+ if (alertLevelKey) {
175
+ // Get the numeric level for this alert
176
+ const alertLevel = kindLevelMap.get(alertLevelKey)
177
+ /* c8 ignore next - impossible */
178
+ if (alertLevel === undefined) continue
179
+
180
+ // Apply the comparison based on the comparator
181
+ switch (comparator) {
182
+ case '>':
183
+ if (alertLevel > kindLevel) {
184
+ matchesComparison = true
185
+ }
186
+ break
187
+ case '<':
188
+ if (alertLevel < kindLevel) {
189
+ matchesComparison = true
190
+ }
191
+ break
192
+ case '>=':
193
+ if (alertLevel >= kindLevel) {
194
+ matchesComparison = true
195
+ }
196
+ break
197
+ case '<=':
198
+ if (alertLevel <= kindLevel) {
199
+ matchesComparison = true
200
+ }
201
+ break
202
+ }
203
+
204
+ // If we found a match, we can stop checking other alerts
205
+ if (matchesComparison) break
206
+ }
207
+ }
208
+
209
+ // Exclude the node if it doesn't match the comparison
210
+ exclude = !matchesComparison
211
+ } else {
212
+ // Original exact match behavior
213
+ const alertName = kindsMap.get(kind)
214
+ exclude = !report.alerts.some(alert => alert.type === alertName)
215
+ }
216
+
217
+ if (exclude) {
218
+ removeNode(state, node)
219
+ }
220
+ }
221
+
222
+ removeDanglingEdges(state)
223
+
224
+ return state
225
+ }
@@ -0,0 +1,9 @@
1
+ import { createSecuritySelectorFilter } from './helpers.ts'
2
+
3
+ /**
4
+ * Filters out any node that does not have a **suspiciousStarActivity** report alert.
5
+ */
6
+ export const suspicious = createSecuritySelectorFilter(
7
+ 'suspicious',
8
+ 'suspiciousStarActivity',
9
+ )
@@ -0,0 +1,9 @@
1
+ import { createSecuritySelectorFilter } from './helpers.ts'
2
+
3
+ /**
4
+ * Filters out any node that does not have a **telemetry** report alert.
5
+ */
6
+ export const tracker = createSecuritySelectorFilter(
7
+ 'tracker',
8
+ 'telemetry',
9
+ )
@@ -0,0 +1,9 @@
1
+ import { createSecuritySelectorFilter } from './helpers.ts'
2
+
3
+ /**
4
+ * Filters out any node that does not have a **trivialPackage** report alert.
5
+ */
6
+ export const trivial = createSecuritySelectorFilter(
7
+ 'trivial',
8
+ 'trivialPackage',
9
+ )
@@ -0,0 +1,26 @@
1
+ import { splitDepID } from '@vltpkg/dep-id/browser'
2
+ import {
3
+ asPostcssNodeWithChildren,
4
+ asTagNode,
5
+ } from '@vltpkg/dss-parser'
6
+ import { removeDanglingEdges, removeNode } from './helpers.ts'
7
+ import type { ParserState } from '../types.ts'
8
+
9
+ /**
10
+ * :type(str) Pseudo-Element will match only nodes that are of
11
+ * the same type as the value used. The type is determined by the
12
+ * first part of the dependency ID.
13
+ */
14
+ export const type = async (state: ParserState) => {
15
+ const type = asPostcssNodeWithChildren(state.current)
16
+ const selector = asPostcssNodeWithChildren(type.nodes[0])
17
+ const name = asTagNode(selector.nodes[0]).value
18
+ for (const node of state.partial.nodes) {
19
+ const nodeType = splitDepID(node.id)[0]
20
+ if (nodeType !== name) {
21
+ removeNode(state, node)
22
+ }
23
+ }
24
+ removeDanglingEdges(state)
25
+ return state
26
+ }
@@ -0,0 +1,9 @@
1
+ import { createSecuritySelectorFilter } from './helpers.ts'
2
+
3
+ /**
4
+ * Filters out any node that does not have a **troll** report alert.
5
+ */
6
+ export const undesirable = createSecuritySelectorFilter(
7
+ 'undesirable',
8
+ 'troll',
9
+ )
@@ -0,0 +1,9 @@
1
+ import { createSecuritySelectorFilter } from './helpers.ts'
2
+
3
+ /**
4
+ * Filters out any node that does not have a **newAuthor** report alert.
5
+ */
6
+ export const unknown = createSecuritySelectorFilter(
7
+ 'unknown',
8
+ 'newAuthor',
9
+ )
@@ -0,0 +1,9 @@
1
+ import { createSecuritySelectorFilter } from './helpers.ts'
2
+
3
+ /**
4
+ * Filters out any node that does not have a **unmaintained** report alert.
5
+ */
6
+ export const unmaintained = createSecuritySelectorFilter(
7
+ 'unmaintained',
8
+ 'unmaintained',
9
+ )