@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/index.ts ADDED
@@ -0,0 +1,620 @@
1
+ import { error } from '@vltpkg/error-cause'
2
+ import { joinDepIDTuple } from '@vltpkg/dep-id/browser'
3
+ import {
4
+ parse,
5
+ isPostcssNodeWithChildren,
6
+ asPostcssNodeWithChildren,
7
+ isSelectorNode,
8
+ isPseudoNode,
9
+ isIdentifierNode,
10
+ isAttributeNode,
11
+ } from '@vltpkg/dss-parser'
12
+ import { attribute } from './attribute.ts'
13
+ import { combinator } from './combinator.ts'
14
+ import { id } from './id.ts'
15
+ import { pseudo } from './pseudo.ts'
16
+ import type { EdgeLike, NodeLike } from '@vltpkg/types'
17
+ import type { SecurityArchiveLike } from '@vltpkg/security-archive'
18
+ import type {
19
+ PostcssNode,
20
+ PostcssNodeWithChildren,
21
+ } from '@vltpkg/dss-parser'
22
+ import type {
23
+ DiffFilesProvider,
24
+ HostContextsMap,
25
+ ParsedSelectorToken,
26
+ ParserState,
27
+ ParserFn,
28
+ QueryResponse,
29
+ QueryResponseNode,
30
+ QueryResponseEdge,
31
+ } from './types.ts'
32
+ import type { DepID } from '@vltpkg/dep-id'
33
+
34
+ export * from './types.ts'
35
+
36
+ export type SearchOptions = {
37
+ signal: AbortSignal
38
+ scopeIDs?: DepID[]
39
+ }
40
+
41
+ const noopFn = async (state: ParserState) => state
42
+
43
+ const selectors = {
44
+ attribute,
45
+ /* c8 ignore start */
46
+ class: async (state: ParserState) => {
47
+ throw error('Unsupported selector', { found: state.current })
48
+ },
49
+ /* c8 ignore end */
50
+ combinator,
51
+ comment: async (state: ParserState) => {
52
+ if (state.current.value && !state.comment) {
53
+ const commentValue = state.current.value
54
+ const cleanComment = commentValue
55
+ .replace(/^\/\*/, '')
56
+ .replace(/\*\/$/, '')
57
+ .trim()
58
+ state.comment = cleanComment
59
+ }
60
+ return state
61
+ },
62
+ id,
63
+ nesting: noopFn,
64
+ pseudo,
65
+ root: noopFn,
66
+ selector: async (state: ParserState) => {
67
+ state.partial.nodes = new Set(state.initial.nodes)
68
+ state.partial.edges = new Set(state.initial.edges)
69
+ return state
70
+ },
71
+ string: async (state: ParserState) => {
72
+ throw error('Unsupported selector', { found: state.current })
73
+ },
74
+ tag: async (state: ParserState) => {
75
+ if (state.current.value !== '{' && state.current.value !== '}') {
76
+ throw error('Unsupported selector', { found: state.current })
77
+ }
78
+ return state
79
+ },
80
+ universal: noopFn,
81
+ }
82
+ const selectorsMap = new Map<string, ParserFn>(
83
+ Object.entries(selectors),
84
+ )
85
+
86
+ export const walk = async (
87
+ state: ParserState,
88
+ ): Promise<ParserState> => {
89
+ await state.cancellable()
90
+
91
+ const parserFn = selectorsMap.get(state.current.type)
92
+
93
+ if (!parserFn) {
94
+ if (state.loose) {
95
+ return state
96
+ }
97
+
98
+ throw error(
99
+ `Missing parser for query node: ${state.current.type}`,
100
+ {
101
+ found: state.current,
102
+ },
103
+ )
104
+ }
105
+ state = await parserFn(state)
106
+
107
+ // pseudo selectors handle their own sub selectors
108
+ if (
109
+ isPostcssNodeWithChildren(state.current) &&
110
+ state.current.type !== 'pseudo'
111
+ ) {
112
+ const node: PostcssNodeWithChildren = asPostcssNodeWithChildren(
113
+ state.current,
114
+ )
115
+
116
+ if (node.nodes.length) {
117
+ for (let i = 0; i < node.nodes.length; i++) {
118
+ const current = node.nodes[i]
119
+ /* c8 ignore next -- impossible but TS doesn't know that */
120
+ if (!current) continue
121
+
122
+ const childState: ParserState = {
123
+ ...state,
124
+ current,
125
+ next: node.nodes[i + 1],
126
+ prev: node.nodes[i - 1],
127
+ }
128
+ state = await walk(childState)
129
+ }
130
+ }
131
+
132
+ if (isSelectorNode(node)) {
133
+ for (const edge of state.partial.edges) {
134
+ state.collect.edges.add(edge)
135
+ }
136
+ for (const node of state.partial.nodes) {
137
+ state.collect.nodes.add(node)
138
+ }
139
+ }
140
+ }
141
+ return state
142
+ }
143
+
144
+ export type QueryOptions = {
145
+ edges: Set<EdgeLike>
146
+ nodes: Set<NodeLike>
147
+ importers: Set<NodeLike>
148
+ retries?: number
149
+ securityArchive: SecurityArchiveLike | undefined
150
+ hostContexts?: HostContextsMap
151
+ diffFiles?: DiffFilesProvider
152
+ }
153
+
154
+ // A list of known security selectors that rely on
155
+ // data from the security-archive in order to work
156
+ const securitySelectors = new Set([
157
+ ':abandoned',
158
+ ':confused',
159
+ ':cve',
160
+ ':cwe',
161
+ ':debug',
162
+ ':deprecated',
163
+ ':dynamic',
164
+ ':entropic',
165
+ ':env',
166
+ ':eval',
167
+ ':fs',
168
+ ':license',
169
+ ':malware',
170
+ ':minified',
171
+ ':native',
172
+ ':network',
173
+ ':obfuscated',
174
+ ':scanned',
175
+ ':score',
176
+ ':sev',
177
+ ':severity',
178
+ ':shell',
179
+ ':shrinkwrap',
180
+ ':squat',
181
+ ':suspicious',
182
+ ':tracker',
183
+ ':trivial',
184
+ ':undesirable',
185
+ ':unknown',
186
+ ':unmaintained',
187
+ ':unpopular',
188
+ ':unstable',
189
+ ':vuln',
190
+ ':vulnerable',
191
+ ])
192
+
193
+ const setMethodToJSON = (node: QueryResponseNode) => {
194
+ const { toJSON } = node
195
+ const insights = node.insights
196
+ node.toJSON = () => ({
197
+ ...toJSON.call(node),
198
+ insights,
199
+ })
200
+ }
201
+
202
+ /**
203
+ * The Query class is used to search the graph for nodes and edges
204
+ * using the Dependency Selector Syntax (DSS).
205
+ */
206
+ export class Query {
207
+ #cache: Map<string, QueryResponse>
208
+ #diffFiles: DiffFilesProvider | undefined
209
+ #edges: Set<EdgeLike>
210
+ #nodes: Set<NodeLike>
211
+ #importers: Set<NodeLike>
212
+ #retries: number
213
+ #securityArchive: SecurityArchiveLike | undefined
214
+ #hostContexts: HostContextsMap | undefined
215
+
216
+ /**
217
+ * Helper method to determine if a given query string is using any of
218
+ * the known security selectors. This is useful so that operations can
219
+ * skip hydrating the security archive if it's not needed.
220
+ */
221
+ static hasSecuritySelectors(query: string): boolean {
222
+ for (const selector of securitySelectors) {
223
+ if (query.includes(selector)) {
224
+ return true
225
+ }
226
+ }
227
+ return false
228
+ }
229
+
230
+ /**
231
+ * Sorts an array of QueryResponse objects by specificity. Objects with
232
+ * higher idCounter values come first, if idCounter values are equal,
233
+ * then objects with higher commonCounter values come first. Otherwise,
234
+ * the original order is preserved.
235
+ */
236
+ static specificitySort(
237
+ responses: QueryResponse[],
238
+ ): QueryResponse[] {
239
+ return [...responses].sort((a, b) => {
240
+ // First compare by idCounter (higher comes first)
241
+ if (a.specificity.idCounter !== b.specificity.idCounter) {
242
+ return b.specificity.idCounter - a.specificity.idCounter
243
+ }
244
+
245
+ // If idCounter values are equal, compare by commonCounter
246
+ if (
247
+ a.specificity.commonCounter !== b.specificity.commonCounter
248
+ ) {
249
+ return (
250
+ b.specificity.commonCounter - a.specificity.commonCounter
251
+ )
252
+ }
253
+
254
+ // If both counters are equal, preserve original order
255
+ return 0
256
+ })
257
+ }
258
+
259
+ constructor({
260
+ edges,
261
+ nodes,
262
+ importers,
263
+ retries,
264
+ securityArchive,
265
+ hostContexts,
266
+ diffFiles,
267
+ }: QueryOptions) {
268
+ this.#cache = new Map()
269
+ this.#diffFiles = diffFiles
270
+ this.#edges = edges
271
+ this.#nodes = nodes
272
+ this.#importers = importers
273
+ this.#retries = retries ?? 3
274
+ this.#securityArchive = securityArchive
275
+ this.#hostContexts = hostContexts
276
+ }
277
+
278
+ #getQueryResponseEdges(_edges: Set<EdgeLike>): QueryResponseEdge[] {
279
+ return Array.from(_edges) as QueryResponseEdge[]
280
+ }
281
+
282
+ #getQueryResponseNodes(_nodes: Set<NodeLike>): QueryResponseNode[] {
283
+ const nodes = Array.from(_nodes) as QueryResponseNode[]
284
+ for (const node of nodes) {
285
+ const securityArchiveEntry = this.#securityArchive?.get(node.id)
286
+
287
+ // if a security archive entry is not found then the insights object
288
+ // should just be empty with scanned=false
289
+ if (!securityArchiveEntry) {
290
+ node.insights = {
291
+ scanned: false,
292
+ }
293
+
294
+ setMethodToJSON(node)
295
+ continue
296
+ }
297
+
298
+ // if a security archive entry is found then we can populate the insights
299
+ node.insights = {
300
+ scanned: true,
301
+ score: securityArchiveEntry.score,
302
+ abandoned: securityArchiveEntry.alerts.some(
303
+ i => i.type === 'missingAuthor',
304
+ ),
305
+ confused: securityArchiveEntry.alerts.some(
306
+ i => i.type === 'manifestConfusion',
307
+ ),
308
+ cve: securityArchiveEntry.alerts
309
+ .map(i => i.props?.cveId)
310
+ .filter(i => i !== undefined),
311
+ cwe: Array.from(
312
+ new Set(
313
+ securityArchiveEntry.alerts
314
+ .filter(i => i.props?.cveId)
315
+ .flatMap(i => i.props?.cwes?.map(j => j.id)),
316
+ ),
317
+ ) as `CWE-${string}`[],
318
+ debug: securityArchiveEntry.alerts.some(
319
+ i => i.type === 'debugAccess',
320
+ ),
321
+ deprecated: securityArchiveEntry.alerts.some(
322
+ i => i.type === 'deprecated',
323
+ ),
324
+ dynamic: securityArchiveEntry.alerts.some(
325
+ i => i.type === 'dynamicRequire',
326
+ ),
327
+ entropic: securityArchiveEntry.alerts.some(
328
+ i => i.type === 'highEntropyStrings',
329
+ ),
330
+ env: securityArchiveEntry.alerts.some(
331
+ i => i.type === 'envVars',
332
+ ),
333
+ eval: securityArchiveEntry.alerts.some(
334
+ i => i.type === 'usesEval',
335
+ ),
336
+ fs: securityArchiveEntry.alerts.some(
337
+ i => i.type === 'filesystemAccess',
338
+ ),
339
+ license: {
340
+ unlicensed: securityArchiveEntry.alerts.some(
341
+ i => i.type === 'explicitlyUnlicensedItem',
342
+ ),
343
+ misc: securityArchiveEntry.alerts.some(
344
+ i => i.type === 'miscLicenseIssues',
345
+ ),
346
+ restricted: securityArchiveEntry.alerts.some(
347
+ i => i.type === 'nonpermissiveLicense',
348
+ ),
349
+ ambiguous: securityArchiveEntry.alerts.some(
350
+ i => i.type === 'ambiguousClassifier',
351
+ ),
352
+ copyleft: securityArchiveEntry.alerts.some(
353
+ i => i.type === 'copyleftLicense',
354
+ ),
355
+ unknown: securityArchiveEntry.alerts.some(
356
+ i => i.type === 'unidentifiedLicense',
357
+ ),
358
+ none: securityArchiveEntry.alerts.some(
359
+ i => i.type === 'noLicenseFound',
360
+ ),
361
+ exception: securityArchiveEntry.alerts.some(
362
+ i => i.type === 'licenseException',
363
+ ),
364
+ },
365
+ malware: securityArchiveEntry.alerts.some(
366
+ i => i.type === 'malware' || i.type === 'gptMalware',
367
+ ),
368
+ minified: securityArchiveEntry.alerts.some(
369
+ i => i.type === 'minifiedFile',
370
+ ),
371
+ native: securityArchiveEntry.alerts.some(
372
+ i => i.type === 'hasNativeCode',
373
+ ),
374
+ network: securityArchiveEntry.alerts.some(
375
+ i => i.type === 'networkAccess',
376
+ ),
377
+ obfuscated: securityArchiveEntry.alerts.some(
378
+ i => i.type === 'obfuscatedFile',
379
+ ),
380
+ scripts: securityArchiveEntry.alerts.some(
381
+ i => i.type === 'installScripts',
382
+ ),
383
+ severity: {
384
+ low: securityArchiveEntry.alerts.some(
385
+ i => i.type === 'mildCVE',
386
+ ),
387
+ medium: securityArchiveEntry.alerts.some(
388
+ i => i.type === 'potentialVulnerability',
389
+ ),
390
+ high: securityArchiveEntry.alerts.some(
391
+ i => i.type === 'cve',
392
+ ),
393
+ critical: securityArchiveEntry.alerts.some(
394
+ i => i.type === 'criticalCVE',
395
+ ),
396
+ },
397
+ shell: securityArchiveEntry.alerts.some(
398
+ i => i.type === 'shellAccess',
399
+ ),
400
+ shrinkwrap: securityArchiveEntry.alerts.some(
401
+ i => i.type === 'shrinkwrap',
402
+ ),
403
+ squat: {
404
+ medium: securityArchiveEntry.alerts.some(
405
+ i => i.type === 'gptDidYouMean',
406
+ ),
407
+ critical: securityArchiveEntry.alerts.some(
408
+ i => i.type === 'didYouMean',
409
+ ),
410
+ },
411
+ suspicious: securityArchiveEntry.alerts.some(
412
+ i => i.type === 'suspiciousStarActivity',
413
+ ),
414
+ tracker: securityArchiveEntry.alerts.some(
415
+ i => i.type === 'telemetry',
416
+ ),
417
+ trivial: securityArchiveEntry.alerts.some(
418
+ i => i.type === 'trivialPackage',
419
+ ),
420
+ undesirable: securityArchiveEntry.alerts.some(
421
+ i => i.type === 'troll',
422
+ ),
423
+ unknown: securityArchiveEntry.alerts.some(
424
+ i => i.type === 'newAuthor',
425
+ ),
426
+ unmaintained: securityArchiveEntry.alerts.some(
427
+ i => i.type === 'unmaintained',
428
+ ),
429
+ unpopular: securityArchiveEntry.alerts.some(
430
+ i => i.type === 'unpopularPackage',
431
+ ),
432
+ unstable: securityArchiveEntry.alerts.some(
433
+ i => i.type === 'unstableOwnership',
434
+ ),
435
+ vuln: {
436
+ low: securityArchiveEntry.alerts.some(
437
+ i => i.type === 'mildCVE' || i.type === 'gptAnomaly',
438
+ ),
439
+ medium: securityArchiveEntry.alerts.some(
440
+ i =>
441
+ i.type === 'potentialVulnerability' ||
442
+ i.type === 'gptSecurity',
443
+ ),
444
+ high: securityArchiveEntry.alerts.some(
445
+ i => i.type === 'cve',
446
+ ),
447
+ critical: securityArchiveEntry.alerts.some(
448
+ i => i.type === 'criticalCVE',
449
+ ),
450
+ },
451
+ }
452
+
453
+ setMethodToJSON(node)
454
+ }
455
+ return nodes
456
+ }
457
+
458
+ /**
459
+ * Search the graph for nodes and edges that match the given query.
460
+ */
461
+ async search(
462
+ query: string,
463
+ {
464
+ signal,
465
+ scopeIDs = [joinDepIDTuple(['file', '.'])],
466
+ }: SearchOptions,
467
+ ): Promise<QueryResponse> {
468
+ if (!query)
469
+ return {
470
+ edges: [],
471
+ nodes: [],
472
+ importers: [],
473
+ comment: '',
474
+ specificity: { idCounter: 0, commonCounter: 0 },
475
+ }
476
+
477
+ const cachedResult = this.#cache.get(query)
478
+ if (cachedResult) {
479
+ return cachedResult
480
+ }
481
+
482
+ const nodes = this.#nodes
483
+ const edges = this.#edges
484
+ const importers = this.#importers
485
+
486
+ // includes virtual workspace edges in the searched edges
487
+ for (const importer of importers) {
488
+ if (!importer.workspaces) continue
489
+ for (const edge of importer.workspaces.values()) {
490
+ edges.add(edge)
491
+ }
492
+ }
493
+
494
+ // parse the query string into AST
495
+ const current = parse(query)
496
+ // set loose mode for the entire parse in case there are multiple selectors
497
+ // so that using invalid pseudo selectors or other query language parser
498
+ // errors won't throw an error,
499
+ // e.g: `:root > *, #a, :foo` still returns results for `:root > ` and `#a`
500
+ // while :foo is ignored
501
+ const loose = asPostcssNodeWithChildren(current).nodes.length > 1
502
+ // builds initial state and walks over it,
503
+ // retrieving the collected result
504
+ const {
505
+ collect,
506
+ comment,
507
+ importers: stateResultImporters,
508
+ specificity,
509
+ } = await walk({
510
+ cancellable: async () => {
511
+ await new Promise(resolve => {
512
+ setTimeout(resolve, 0)
513
+ })
514
+ signal.throwIfAborted()
515
+ },
516
+ current,
517
+ initial: {
518
+ nodes: new Set(nodes),
519
+ edges: new Set(edges),
520
+ },
521
+ collect: {
522
+ nodes: new Set<NodeLike>(),
523
+ edges: new Set<EdgeLike>(),
524
+ },
525
+ comment: '',
526
+ diffFiles: this.#diffFiles,
527
+ loose,
528
+ importers,
529
+ partial: { nodes, edges },
530
+ retries: this.#retries,
531
+ signal,
532
+ securityArchive: this.#securityArchive,
533
+ scopeIDs,
534
+ walk,
535
+ specificity: { idCounter: 0, commonCounter: 0 },
536
+ hostContexts: this.#hostContexts,
537
+ })
538
+
539
+ const res: QueryResponse = {
540
+ edges: this.#getQueryResponseEdges(collect.edges),
541
+ nodes: this.#getQueryResponseNodes(collect.nodes),
542
+ importers: this.#getQueryResponseNodes(stateResultImporters),
543
+ comment,
544
+ specificity,
545
+ }
546
+ this.#cache.set(query, res)
547
+ return res
548
+ }
549
+
550
+ /**
551
+ * Parses a query string in order to retrieve an array of tokens.
552
+ */
553
+ static getQueryTokens(query: string): ParsedSelectorToken[] {
554
+ if (!query) return []
555
+
556
+ const tokens: ParsedSelectorToken[] = []
557
+
558
+ const ast = (q: string) => {
559
+ try {
560
+ return parse(q)
561
+ } catch (_e) {
562
+ return ast(q.slice(0, -1))
563
+ }
564
+ }
565
+
566
+ const processNode = (node: PostcssNode) => {
567
+ for (const key of selectorsMap.keys()) {
568
+ if (node.type === key && node.type !== 'root') {
569
+ let token = `${node.spaces.before}${node.value}${node.spaces.after}`
570
+
571
+ if (isIdentifierNode(node)) {
572
+ token = `${node.spaces.before}#${node.value}${node.spaces.after}`
573
+ } else if (isSelectorNode(node)) {
574
+ token = `${node.spaces.before},${node.spaces.after}`
575
+ } else if (isAttributeNode(node)) {
576
+ token = String(
577
+ node.source?.start?.column &&
578
+ node.source.end?.column &&
579
+ `${node.spaces.before}${query.slice(node.source.start.column - 1, node.source.end.column)}${node.spaces.after}`,
580
+ )
581
+ }
582
+
583
+ if (
584
+ isPostcssNodeWithChildren(node) &&
585
+ isPseudoNode(node) &&
586
+ node.nodes.length
587
+ ) {
588
+ token = String(token.split('(')[0])
589
+ token += '('
590
+ }
591
+
592
+ if (
593
+ !isSelectorNode(node) ||
594
+ node.parent?.nodes.indexOf(node) !== 0
595
+ ) {
596
+ tokens.push({
597
+ ...node,
598
+ token,
599
+ } as ParsedSelectorToken)
600
+ }
601
+ }
602
+ }
603
+ if (isPostcssNodeWithChildren(node)) {
604
+ for (const child of node.nodes) {
605
+ processNode(child)
606
+ }
607
+ if (isPseudoNode(node) && node.nodes.length) {
608
+ tokens.push({
609
+ ...node,
610
+ token: ')' + node.spaces.after,
611
+ type: 'pseudo',
612
+ } as ParsedSelectorToken)
613
+ }
614
+ }
615
+ }
616
+
617
+ processNode(ast(query))
618
+ return tokens
619
+ }
620
+ }