@cyvest/cyvest-js 2.0.1
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.
- package/README.md +51 -0
- package/dist/index.d.mts +1210 -0
- package/dist/index.d.ts +1210 -0
- package/dist/index.js +1768 -0
- package/dist/index.mjs +1632 -0
- package/package.json +27 -0
- package/src/finders.ts +712 -0
- package/src/getters.ts +409 -0
- package/src/graph.ts +601 -0
- package/src/helpers.ts +31 -0
- package/src/index.ts +28 -0
- package/src/keys.ts +286 -0
- package/src/levels.ts +262 -0
- package/src/types.generated.ts +176 -0
- package/tests/getters-finders.test.ts +461 -0
- package/tests/graph.test.ts +398 -0
- package/tests/keys-levels.test.ts +298 -0
- package/tsconfig.json +4 -0
package/src/finders.ts
ADDED
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finder utilities for querying and filtering Cyvest Investigation data.
|
|
3
|
+
*
|
|
4
|
+
* These functions provide filtering, searching, and cross-referencing
|
|
5
|
+
* capabilities for observables, checks, and threat intel.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
CyvestInvestigation,
|
|
10
|
+
Observable,
|
|
11
|
+
Check,
|
|
12
|
+
ThreatIntel,
|
|
13
|
+
Container,
|
|
14
|
+
Level,
|
|
15
|
+
} from "./types.generated";
|
|
16
|
+
import { isLevelAtLeast, isLevelHigherThan, LEVEL_VALUES } from "./levels";
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Observable Finders
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Find all observables of a specific type.
|
|
24
|
+
*
|
|
25
|
+
* @param inv - The investigation to search
|
|
26
|
+
* @param type - Observable type (e.g., "ipv4-addr", "url", "domain-name")
|
|
27
|
+
* @returns Array of matching observables
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* const ips = findObservablesByType(investigation, "ipv4-addr");
|
|
32
|
+
* const urls = findObservablesByType(investigation, "url");
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export function findObservablesByType(
|
|
36
|
+
inv: CyvestInvestigation,
|
|
37
|
+
type: string
|
|
38
|
+
): Observable[] {
|
|
39
|
+
const normalizedType = type.trim().toLowerCase();
|
|
40
|
+
return Object.values(inv.observables).filter(
|
|
41
|
+
(obs) => obs.type.toLowerCase() === normalizedType
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Find all observables at a specific level.
|
|
47
|
+
*
|
|
48
|
+
* @param inv - The investigation to search
|
|
49
|
+
* @param level - Security level to filter by
|
|
50
|
+
* @returns Array of matching observables
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* const malicious = findObservablesByLevel(investigation, "MALICIOUS");
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export function findObservablesByLevel(
|
|
58
|
+
inv: CyvestInvestigation,
|
|
59
|
+
level: Level
|
|
60
|
+
): Observable[] {
|
|
61
|
+
return Object.values(inv.observables).filter((obs) => obs.level === level);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Find all observables at or above a minimum level.
|
|
66
|
+
*
|
|
67
|
+
* @param inv - The investigation to search
|
|
68
|
+
* @param minLevel - Minimum security level
|
|
69
|
+
* @returns Array of matching observables
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* const suspicious = findObservablesAtLeast(investigation, "SUSPICIOUS");
|
|
74
|
+
* // Returns SUSPICIOUS and MALICIOUS observables
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export function findObservablesAtLeast(
|
|
78
|
+
inv: CyvestInvestigation,
|
|
79
|
+
minLevel: Level
|
|
80
|
+
): Observable[] {
|
|
81
|
+
return Object.values(inv.observables).filter((obs) =>
|
|
82
|
+
isLevelAtLeast(obs.level, minLevel)
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Find observables by exact value match.
|
|
88
|
+
*
|
|
89
|
+
* @param inv - The investigation to search
|
|
90
|
+
* @param value - Value to search for
|
|
91
|
+
* @param caseSensitive - Whether to perform case-sensitive match (default: false)
|
|
92
|
+
* @returns Array of matching observables
|
|
93
|
+
*/
|
|
94
|
+
export function findObservablesByValue(
|
|
95
|
+
inv: CyvestInvestigation,
|
|
96
|
+
value: string,
|
|
97
|
+
caseSensitive = false
|
|
98
|
+
): Observable[] {
|
|
99
|
+
const searchValue = caseSensitive ? value : value.toLowerCase();
|
|
100
|
+
return Object.values(inv.observables).filter((obs) => {
|
|
101
|
+
const obsValue = caseSensitive ? obs.value : obs.value.toLowerCase();
|
|
102
|
+
return obsValue === searchValue;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Find observables containing a substring in their value.
|
|
108
|
+
*
|
|
109
|
+
* @param inv - The investigation to search
|
|
110
|
+
* @param substring - Substring to search for
|
|
111
|
+
* @param caseSensitive - Whether to perform case-sensitive match (default: false)
|
|
112
|
+
* @returns Array of matching observables
|
|
113
|
+
*/
|
|
114
|
+
export function findObservablesContaining(
|
|
115
|
+
inv: CyvestInvestigation,
|
|
116
|
+
substring: string,
|
|
117
|
+
caseSensitive = false
|
|
118
|
+
): Observable[] {
|
|
119
|
+
const searchStr = caseSensitive ? substring : substring.toLowerCase();
|
|
120
|
+
return Object.values(inv.observables).filter((obs) => {
|
|
121
|
+
const obsValue = caseSensitive ? obs.value : obs.value.toLowerCase();
|
|
122
|
+
return obsValue.includes(searchStr);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Find observables matching a regular expression.
|
|
128
|
+
*
|
|
129
|
+
* @param inv - The investigation to search
|
|
130
|
+
* @param pattern - Regular expression pattern
|
|
131
|
+
* @returns Array of matching observables
|
|
132
|
+
*/
|
|
133
|
+
export function findObservablesMatching(
|
|
134
|
+
inv: CyvestInvestigation,
|
|
135
|
+
pattern: RegExp
|
|
136
|
+
): Observable[] {
|
|
137
|
+
return Object.values(inv.observables).filter((obs) => pattern.test(obs.value));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Find internal observables.
|
|
142
|
+
*
|
|
143
|
+
* @param inv - The investigation to search
|
|
144
|
+
* @returns Array of internal observables
|
|
145
|
+
*/
|
|
146
|
+
export function findInternalObservables(inv: CyvestInvestigation): Observable[] {
|
|
147
|
+
return Object.values(inv.observables).filter((obs) => obs.internal);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Find external (non-internal) observables.
|
|
152
|
+
*
|
|
153
|
+
* @param inv - The investigation to search
|
|
154
|
+
* @returns Array of external observables
|
|
155
|
+
*/
|
|
156
|
+
export function findExternalObservables(inv: CyvestInvestigation): Observable[] {
|
|
157
|
+
return Object.values(inv.observables).filter((obs) => !obs.internal);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Find whitelisted observables.
|
|
162
|
+
*
|
|
163
|
+
* @param inv - The investigation to search
|
|
164
|
+
* @returns Array of whitelisted observables
|
|
165
|
+
*/
|
|
166
|
+
export function findWhitelistedObservables(
|
|
167
|
+
inv: CyvestInvestigation
|
|
168
|
+
): Observable[] {
|
|
169
|
+
return Object.values(inv.observables).filter((obs) => obs.whitelisted);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Find observables with threat intelligence data.
|
|
174
|
+
*
|
|
175
|
+
* @param inv - The investigation to search
|
|
176
|
+
* @returns Array of observables that have associated threat intel
|
|
177
|
+
*/
|
|
178
|
+
export function findObservablesWithThreatIntel(
|
|
179
|
+
inv: CyvestInvestigation
|
|
180
|
+
): Observable[] {
|
|
181
|
+
return Object.values(inv.observables).filter(
|
|
182
|
+
(obs) => obs.threat_intels.length > 0
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ============================================================================
|
|
187
|
+
// Check Finders
|
|
188
|
+
// ============================================================================
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Find all checks in a specific scope.
|
|
192
|
+
*
|
|
193
|
+
* @param inv - The investigation to search
|
|
194
|
+
* @param scope - Check scope
|
|
195
|
+
* @returns Array of checks in the scope
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```ts
|
|
199
|
+
* const emailChecks = findChecksByScope(investigation, "email_headers");
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
export function findChecksByScope(
|
|
203
|
+
inv: CyvestInvestigation,
|
|
204
|
+
scope: string
|
|
205
|
+
): Check[] {
|
|
206
|
+
const normalizedScope = scope.trim().toLowerCase();
|
|
207
|
+
|
|
208
|
+
// Try direct lookup first
|
|
209
|
+
if (inv.checks[scope]) {
|
|
210
|
+
return inv.checks[scope];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Fallback to normalized search
|
|
214
|
+
for (const [key, checks] of Object.entries(inv.checks)) {
|
|
215
|
+
if (key.toLowerCase() === normalizedScope) {
|
|
216
|
+
return checks;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Find all checks at a specific level.
|
|
225
|
+
*
|
|
226
|
+
* @param inv - The investigation to search
|
|
227
|
+
* @param level - Security level to filter by
|
|
228
|
+
* @returns Array of matching checks
|
|
229
|
+
*/
|
|
230
|
+
export function findChecksByLevel(
|
|
231
|
+
inv: CyvestInvestigation,
|
|
232
|
+
level: Level
|
|
233
|
+
): Check[] {
|
|
234
|
+
const result: Check[] = [];
|
|
235
|
+
for (const checks of Object.values(inv.checks)) {
|
|
236
|
+
for (const check of checks) {
|
|
237
|
+
if (check.level === level) {
|
|
238
|
+
result.push(check);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return result;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Find all checks at or above a minimum level.
|
|
247
|
+
*
|
|
248
|
+
* @param inv - The investigation to search
|
|
249
|
+
* @param minLevel - Minimum security level
|
|
250
|
+
* @returns Array of matching checks
|
|
251
|
+
*/
|
|
252
|
+
export function findChecksAtLeast(
|
|
253
|
+
inv: CyvestInvestigation,
|
|
254
|
+
minLevel: Level
|
|
255
|
+
): Check[] {
|
|
256
|
+
const result: Check[] = [];
|
|
257
|
+
for (const checks of Object.values(inv.checks)) {
|
|
258
|
+
for (const check of checks) {
|
|
259
|
+
if (isLevelAtLeast(check.level, minLevel)) {
|
|
260
|
+
result.push(check);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return result;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Find checks by check ID (across all scopes).
|
|
269
|
+
*
|
|
270
|
+
* @param inv - The investigation to search
|
|
271
|
+
* @param checkId - Check identifier to search for
|
|
272
|
+
* @returns Array of matching checks
|
|
273
|
+
*/
|
|
274
|
+
export function findChecksByCheckId(
|
|
275
|
+
inv: CyvestInvestigation,
|
|
276
|
+
checkId: string
|
|
277
|
+
): Check[] {
|
|
278
|
+
const normalizedId = checkId.trim().toLowerCase();
|
|
279
|
+
const result: Check[] = [];
|
|
280
|
+
|
|
281
|
+
for (const checks of Object.values(inv.checks)) {
|
|
282
|
+
for (const check of checks) {
|
|
283
|
+
if (check.check_id.toLowerCase() === normalizedId) {
|
|
284
|
+
result.push(check);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return result;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Find checks with score policy set to manual.
|
|
293
|
+
*
|
|
294
|
+
* @param inv - The investigation to search
|
|
295
|
+
* @returns Array of manually scored checks
|
|
296
|
+
*/
|
|
297
|
+
export function findManuallyScored(inv: CyvestInvestigation): Check[] {
|
|
298
|
+
const result: Check[] = [];
|
|
299
|
+
for (const checks of Object.values(inv.checks)) {
|
|
300
|
+
for (const check of checks) {
|
|
301
|
+
if (check.score_policy === "manual") {
|
|
302
|
+
result.push(check);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return result;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ============================================================================
|
|
310
|
+
// Threat Intel Finders
|
|
311
|
+
// ============================================================================
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Find all threat intel from a specific source.
|
|
315
|
+
*
|
|
316
|
+
* @param inv - The investigation to search
|
|
317
|
+
* @param source - Source name (e.g., "virustotal", "otx")
|
|
318
|
+
* @returns Array of threat intel from the source
|
|
319
|
+
*/
|
|
320
|
+
export function findThreatIntelBySource(
|
|
321
|
+
inv: CyvestInvestigation,
|
|
322
|
+
source: string
|
|
323
|
+
): ThreatIntel[] {
|
|
324
|
+
const normalizedSource = source.trim().toLowerCase();
|
|
325
|
+
return Object.values(inv.threat_intels).filter(
|
|
326
|
+
(ti) => ti.source.toLowerCase() === normalizedSource
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Find all threat intel at a specific level.
|
|
332
|
+
*
|
|
333
|
+
* @param inv - The investigation to search
|
|
334
|
+
* @param level - Security level to filter by
|
|
335
|
+
* @returns Array of matching threat intel
|
|
336
|
+
*/
|
|
337
|
+
export function findThreatIntelByLevel(
|
|
338
|
+
inv: CyvestInvestigation,
|
|
339
|
+
level: Level
|
|
340
|
+
): ThreatIntel[] {
|
|
341
|
+
return Object.values(inv.threat_intels).filter((ti) => ti.level === level);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Find all threat intel at or above a minimum level.
|
|
346
|
+
*
|
|
347
|
+
* @param inv - The investigation to search
|
|
348
|
+
* @param minLevel - Minimum security level
|
|
349
|
+
* @returns Array of matching threat intel
|
|
350
|
+
*/
|
|
351
|
+
export function findThreatIntelAtLeast(
|
|
352
|
+
inv: CyvestInvestigation,
|
|
353
|
+
minLevel: Level
|
|
354
|
+
): ThreatIntel[] {
|
|
355
|
+
return Object.values(inv.threat_intels).filter((ti) =>
|
|
356
|
+
isLevelAtLeast(ti.level, minLevel)
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ============================================================================
|
|
361
|
+
// Container Finders
|
|
362
|
+
// ============================================================================
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Find containers at a specific aggregated level.
|
|
366
|
+
*
|
|
367
|
+
* @param inv - The investigation to search
|
|
368
|
+
* @param level - Aggregated level to filter by
|
|
369
|
+
* @returns Array of matching containers
|
|
370
|
+
*/
|
|
371
|
+
export function findContainersByLevel(
|
|
372
|
+
inv: CyvestInvestigation,
|
|
373
|
+
level: Level
|
|
374
|
+
): Container[] {
|
|
375
|
+
const result: Container[] = [];
|
|
376
|
+
|
|
377
|
+
function searchContainers(containers: Record<string, Container>): void {
|
|
378
|
+
for (const container of Object.values(containers)) {
|
|
379
|
+
if (container.aggregated_level === level) {
|
|
380
|
+
result.push(container);
|
|
381
|
+
}
|
|
382
|
+
searchContainers(container.sub_containers);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
searchContainers(inv.containers);
|
|
387
|
+
return result;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Find containers at or above a minimum aggregated level.
|
|
392
|
+
*
|
|
393
|
+
* @param inv - The investigation to search
|
|
394
|
+
* @param minLevel - Minimum aggregated level
|
|
395
|
+
* @returns Array of matching containers
|
|
396
|
+
*/
|
|
397
|
+
export function findContainersAtLeast(
|
|
398
|
+
inv: CyvestInvestigation,
|
|
399
|
+
minLevel: Level
|
|
400
|
+
): Container[] {
|
|
401
|
+
const result: Container[] = [];
|
|
402
|
+
|
|
403
|
+
function searchContainers(containers: Record<string, Container>): void {
|
|
404
|
+
for (const container of Object.values(containers)) {
|
|
405
|
+
if (isLevelAtLeast(container.aggregated_level, minLevel)) {
|
|
406
|
+
result.push(container);
|
|
407
|
+
}
|
|
408
|
+
searchContainers(container.sub_containers);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
searchContainers(inv.containers);
|
|
413
|
+
return result;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ============================================================================
|
|
417
|
+
// Cross-Reference Finders
|
|
418
|
+
// ============================================================================
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Get all checks that generated or reference a specific observable.
|
|
422
|
+
*
|
|
423
|
+
* @param inv - The investigation to search
|
|
424
|
+
* @param observableKey - Key of the observable
|
|
425
|
+
* @returns Array of checks that reference this observable
|
|
426
|
+
*
|
|
427
|
+
* @example
|
|
428
|
+
* ```ts
|
|
429
|
+
* const checks = getChecksForObservable(investigation, "obs:ipv4-addr:192.168.1.1");
|
|
430
|
+
* ```
|
|
431
|
+
*/
|
|
432
|
+
export function getChecksForObservable(
|
|
433
|
+
inv: CyvestInvestigation,
|
|
434
|
+
observableKey: string
|
|
435
|
+
): Check[] {
|
|
436
|
+
const result: Check[] = [];
|
|
437
|
+
|
|
438
|
+
for (const checks of Object.values(inv.checks)) {
|
|
439
|
+
for (const check of checks) {
|
|
440
|
+
if (check.observables.includes(observableKey)) {
|
|
441
|
+
result.push(check);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return result;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Get all threat intel entries for a specific observable.
|
|
451
|
+
*
|
|
452
|
+
* @param inv - The investigation to search
|
|
453
|
+
* @param observableKey - Key of the observable
|
|
454
|
+
* @returns Array of threat intel for this observable
|
|
455
|
+
*/
|
|
456
|
+
export function getThreatIntelsForObservable(
|
|
457
|
+
inv: CyvestInvestigation,
|
|
458
|
+
observableKey: string
|
|
459
|
+
): ThreatIntel[] {
|
|
460
|
+
// First try using the observable's threat_intels array
|
|
461
|
+
const observable = inv.observables[observableKey];
|
|
462
|
+
if (observable) {
|
|
463
|
+
return observable.threat_intels
|
|
464
|
+
.map((tiKey) => inv.threat_intels[tiKey])
|
|
465
|
+
.filter((ti): ti is ThreatIntel => ti !== undefined);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Fallback: search all threat intel
|
|
469
|
+
return Object.values(inv.threat_intels).filter(
|
|
470
|
+
(ti) => ti.observable_key === observableKey
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Get all observables referenced by a specific check.
|
|
476
|
+
*
|
|
477
|
+
* @param inv - The investigation to search
|
|
478
|
+
* @param checkKey - Key of the check
|
|
479
|
+
* @returns Array of observables referenced by this check
|
|
480
|
+
*/
|
|
481
|
+
export function getObservablesForCheck(
|
|
482
|
+
inv: CyvestInvestigation,
|
|
483
|
+
checkKey: string
|
|
484
|
+
): Observable[] {
|
|
485
|
+
// Find the check
|
|
486
|
+
for (const checks of Object.values(inv.checks)) {
|
|
487
|
+
for (const check of checks) {
|
|
488
|
+
if (check.key === checkKey) {
|
|
489
|
+
return check.observables
|
|
490
|
+
.map((obsKey) => inv.observables[obsKey])
|
|
491
|
+
.filter((obs): obs is Observable => obs !== undefined);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return [];
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Get all checks for a specific container.
|
|
500
|
+
*
|
|
501
|
+
* @param inv - The investigation to search
|
|
502
|
+
* @param containerKey - Key of the container
|
|
503
|
+
* @param recursive - Include checks from sub-containers (default: false)
|
|
504
|
+
* @returns Array of checks in the container
|
|
505
|
+
*/
|
|
506
|
+
export function getChecksForContainer(
|
|
507
|
+
inv: CyvestInvestigation,
|
|
508
|
+
containerKey: string,
|
|
509
|
+
recursive = false
|
|
510
|
+
): Check[] {
|
|
511
|
+
const result: Check[] = [];
|
|
512
|
+
|
|
513
|
+
function findContainer(
|
|
514
|
+
containers: Record<string, Container>
|
|
515
|
+
): Container | undefined {
|
|
516
|
+
for (const container of Object.values(containers)) {
|
|
517
|
+
if (container.key === containerKey) {
|
|
518
|
+
return container;
|
|
519
|
+
}
|
|
520
|
+
const found = findContainer(container.sub_containers);
|
|
521
|
+
if (found) return found;
|
|
522
|
+
}
|
|
523
|
+
return undefined;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function collectChecks(container: Container): void {
|
|
527
|
+
for (const checkKey of container.checks) {
|
|
528
|
+
for (const checks of Object.values(inv.checks)) {
|
|
529
|
+
for (const check of checks) {
|
|
530
|
+
if (check.key === checkKey) {
|
|
531
|
+
result.push(check);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (recursive) {
|
|
538
|
+
for (const subContainer of Object.values(container.sub_containers)) {
|
|
539
|
+
collectChecks(subContainer);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const container = findContainer(inv.containers);
|
|
545
|
+
if (container) {
|
|
546
|
+
collectChecks(container);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return result;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// ============================================================================
|
|
553
|
+
// Sorting Utilities
|
|
554
|
+
// ============================================================================
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Sort observables by score (descending - highest first).
|
|
558
|
+
*
|
|
559
|
+
* @param observables - Array of observables to sort
|
|
560
|
+
* @returns Sorted array (new array, doesn't mutate input)
|
|
561
|
+
*/
|
|
562
|
+
export function sortObservablesByScore(observables: Observable[]): Observable[] {
|
|
563
|
+
return [...observables].sort((a, b) => b.score - a.score);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Sort checks by score (descending - highest first).
|
|
568
|
+
*
|
|
569
|
+
* @param checks - Array of checks to sort
|
|
570
|
+
* @returns Sorted array (new array, doesn't mutate input)
|
|
571
|
+
*/
|
|
572
|
+
export function sortChecksByScore(checks: Check[]): Check[] {
|
|
573
|
+
return [...checks].sort((a, b) => b.score - a.score);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Sort observables by level (descending - most severe first).
|
|
578
|
+
*
|
|
579
|
+
* @param observables - Array of observables to sort
|
|
580
|
+
* @returns Sorted array (new array, doesn't mutate input)
|
|
581
|
+
*/
|
|
582
|
+
export function sortObservablesByLevel(observables: Observable[]): Observable[] {
|
|
583
|
+
return [...observables].sort(
|
|
584
|
+
(a, b) => LEVEL_VALUES[b.level] - LEVEL_VALUES[a.level]
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Sort checks by level (descending - most severe first).
|
|
590
|
+
*
|
|
591
|
+
* @param checks - Array of checks to sort
|
|
592
|
+
* @returns Sorted array (new array, doesn't mutate input)
|
|
593
|
+
*/
|
|
594
|
+
export function sortChecksByLevel(checks: Check[]): Check[] {
|
|
595
|
+
return [...checks].sort(
|
|
596
|
+
(a, b) => LEVEL_VALUES[b.level] - LEVEL_VALUES[a.level]
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// ============================================================================
|
|
601
|
+
// Aggregation Utilities
|
|
602
|
+
// ============================================================================
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Get the highest scoring observables.
|
|
606
|
+
*
|
|
607
|
+
* @param inv - The investigation to search
|
|
608
|
+
* @param n - Number of results to return (default: 10)
|
|
609
|
+
* @returns Array of highest scoring observables
|
|
610
|
+
*/
|
|
611
|
+
export function getHighestScoringObservables(
|
|
612
|
+
inv: CyvestInvestigation,
|
|
613
|
+
n = 10
|
|
614
|
+
): Observable[] {
|
|
615
|
+
return sortObservablesByScore(Object.values(inv.observables)).slice(0, n);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Get the highest scoring checks.
|
|
620
|
+
*
|
|
621
|
+
* @param inv - The investigation to search
|
|
622
|
+
* @param n - Number of results to return (default: 10)
|
|
623
|
+
* @returns Array of highest scoring checks
|
|
624
|
+
*/
|
|
625
|
+
export function getHighestScoringChecks(
|
|
626
|
+
inv: CyvestInvestigation,
|
|
627
|
+
n = 10
|
|
628
|
+
): Check[] {
|
|
629
|
+
const allChecks: Check[] = [];
|
|
630
|
+
for (const checks of Object.values(inv.checks)) {
|
|
631
|
+
allChecks.push(...checks);
|
|
632
|
+
}
|
|
633
|
+
return sortChecksByScore(allChecks).slice(0, n);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Get all malicious observables (convenience function).
|
|
638
|
+
*
|
|
639
|
+
* @param inv - The investigation to search
|
|
640
|
+
* @returns Array of malicious observables
|
|
641
|
+
*/
|
|
642
|
+
export function getMaliciousObservables(inv: CyvestInvestigation): Observable[] {
|
|
643
|
+
return findObservablesByLevel(inv, "MALICIOUS");
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Get all suspicious observables (convenience function).
|
|
648
|
+
*
|
|
649
|
+
* @param inv - The investigation to search
|
|
650
|
+
* @returns Array of suspicious observables
|
|
651
|
+
*/
|
|
652
|
+
export function getSuspiciousObservables(inv: CyvestInvestigation): Observable[] {
|
|
653
|
+
return findObservablesByLevel(inv, "SUSPICIOUS");
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Get all malicious checks (convenience function).
|
|
658
|
+
*
|
|
659
|
+
* @param inv - The investigation to search
|
|
660
|
+
* @returns Array of malicious checks
|
|
661
|
+
*/
|
|
662
|
+
export function getMaliciousChecks(inv: CyvestInvestigation): Check[] {
|
|
663
|
+
return findChecksByLevel(inv, "MALICIOUS");
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Get all suspicious checks (convenience function).
|
|
668
|
+
*
|
|
669
|
+
* @param inv - The investigation to search
|
|
670
|
+
* @returns Array of suspicious checks
|
|
671
|
+
*/
|
|
672
|
+
export function getSuspiciousChecks(inv: CyvestInvestigation): Check[] {
|
|
673
|
+
return findChecksByLevel(inv, "SUSPICIOUS");
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Get all scopes that have checks.
|
|
678
|
+
*
|
|
679
|
+
* @param inv - The investigation
|
|
680
|
+
* @returns Array of scope names
|
|
681
|
+
*/
|
|
682
|
+
export function getAllScopes(inv: CyvestInvestigation): string[] {
|
|
683
|
+
return Object.keys(inv.checks);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Get all observable types present in the investigation.
|
|
688
|
+
*
|
|
689
|
+
* @param inv - The investigation
|
|
690
|
+
* @returns Array of unique observable types
|
|
691
|
+
*/
|
|
692
|
+
export function getAllObservableTypes(inv: CyvestInvestigation): string[] {
|
|
693
|
+
const types = new Set<string>();
|
|
694
|
+
for (const obs of Object.values(inv.observables)) {
|
|
695
|
+
types.add(obs.type);
|
|
696
|
+
}
|
|
697
|
+
return Array.from(types);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Get all threat intel sources present in the investigation.
|
|
702
|
+
*
|
|
703
|
+
* @param inv - The investigation
|
|
704
|
+
* @returns Array of unique source names
|
|
705
|
+
*/
|
|
706
|
+
export function getAllThreatIntelSources(inv: CyvestInvestigation): string[] {
|
|
707
|
+
const sources = new Set<string>();
|
|
708
|
+
for (const ti of Object.values(inv.threat_intels)) {
|
|
709
|
+
sources.add(ti.source);
|
|
710
|
+
}
|
|
711
|
+
return Array.from(sources);
|
|
712
|
+
}
|