@cyvest/cyvest-js 3.1.0 → 4.0.0

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/dist/index.mjs CHANGED
@@ -5,6 +5,97 @@ import addFormats from "ajv-formats";
5
5
  // ../../../schema/cyvest.schema.json
6
6
  var cyvest_schema_default = {
7
7
  $defs: {
8
+ AuditEvent: {
9
+ additionalProperties: true,
10
+ description: "Centralized audit event for investigation-level changes.",
11
+ properties: {
12
+ event_id: {
13
+ title: "Event Id",
14
+ type: "string"
15
+ },
16
+ timestamp: {
17
+ format: "date-time",
18
+ title: "Timestamp",
19
+ type: "string"
20
+ },
21
+ event_type: {
22
+ title: "Event Type",
23
+ type: "string"
24
+ },
25
+ actor: {
26
+ anyOf: [
27
+ {
28
+ type: "string"
29
+ },
30
+ {
31
+ type: "null"
32
+ }
33
+ ],
34
+ default: null,
35
+ title: "Actor"
36
+ },
37
+ reason: {
38
+ anyOf: [
39
+ {
40
+ type: "string"
41
+ },
42
+ {
43
+ type: "null"
44
+ }
45
+ ],
46
+ default: null,
47
+ title: "Reason"
48
+ },
49
+ tool: {
50
+ anyOf: [
51
+ {
52
+ type: "string"
53
+ },
54
+ {
55
+ type: "null"
56
+ }
57
+ ],
58
+ default: null,
59
+ title: "Tool"
60
+ },
61
+ object_type: {
62
+ anyOf: [
63
+ {
64
+ type: "string"
65
+ },
66
+ {
67
+ type: "null"
68
+ }
69
+ ],
70
+ default: null,
71
+ title: "Object Type"
72
+ },
73
+ object_key: {
74
+ anyOf: [
75
+ {
76
+ type: "string"
77
+ },
78
+ {
79
+ type: "null"
80
+ }
81
+ ],
82
+ default: null,
83
+ title: "Object Key"
84
+ },
85
+ details: {
86
+ additionalProperties: true,
87
+ title: "Details",
88
+ type: "object"
89
+ }
90
+ },
91
+ required: [
92
+ "event_id",
93
+ "timestamp",
94
+ "event_type"
95
+ ],
96
+ title: "AuditEvent",
97
+ type: "object"
98
+ },
8
99
  Check: {
9
100
  description: "Represents a verification step in the investigation.\n\nA check validates a specific aspect of the data under investigation\nand contributes to the overall investigation score.",
10
101
  properties: {
@@ -36,20 +127,25 @@ var cyvest_schema_default = {
36
127
  level: {
37
128
  $ref: "#/$defs/Level"
38
129
  },
39
- observables: {
130
+ origin_investigation_id: {
131
+ title: "Origin Investigation Id",
132
+ type: "string"
133
+ },
134
+ observable_links: {
40
135
  items: {
41
- type: "string"
136
+ $ref: "#/$defs/ObservableLink"
42
137
  },
43
- title: "Observables",
138
+ title: "Observable Links",
44
139
  type: "array"
45
140
  },
46
- score_policy: {
47
- $ref: "#/$defs/CheckScorePolicy",
48
- default: "auto"
49
- },
50
141
  key: {
51
142
  title: "Key",
52
143
  type: "string"
144
+ },
145
+ score_display: {
146
+ readOnly: true,
147
+ title: "Score Display",
148
+ type: "string"
53
149
  }
54
150
  },
55
151
  required: [
@@ -60,21 +156,14 @@ var cyvest_schema_default = {
60
156
  "extra",
61
157
  "score",
62
158
  "level",
63
- "observables",
64
- "key"
159
+ "origin_investigation_id",
160
+ "observable_links",
161
+ "key",
162
+ "score_display"
65
163
  ],
66
164
  title: "Check",
67
165
  type: "object"
68
166
  },
69
- CheckScorePolicy: {
70
- description: "Controls how a check reacts to linked observables.",
71
- enum: [
72
- "auto",
73
- "manual"
74
- ],
75
- title: "CheckScorePolicy",
76
- type: "string"
77
- },
78
167
  Container: {
79
168
  additionalProperties: false,
80
169
  description: "Groups checks and sub-containers for hierarchical organization.\n\nContainers allow structuring the investigation into logical sections\nwith aggregated scores and levels.",
@@ -284,14 +373,19 @@ var cyvest_schema_default = {
284
373
  title: "Key",
285
374
  type: "string"
286
375
  },
287
- generated_by_checks: {
288
- description: "Checks that generated this observable.",
376
+ check_links: {
377
+ description: "Checks that currently link to this observable (navigation-only).",
289
378
  items: {
290
379
  type: "string"
291
380
  },
292
381
  readOnly: true,
293
- title: "Generated By Checks",
382
+ title: "Check Links",
294
383
  type: "array"
384
+ },
385
+ score_display: {
386
+ readOnly: true,
387
+ title: "Score Display",
388
+ type: "string"
295
389
  }
296
390
  },
297
391
  required: [
@@ -306,11 +400,40 @@ var cyvest_schema_default = {
306
400
  "threat_intels",
307
401
  "relationships",
308
402
  "key",
309
- "generated_by_checks"
403
+ "check_links",
404
+ "score_display"
310
405
  ],
311
406
  title: "Observable",
312
407
  type: "object"
313
408
  },
409
+ ObservableLink: {
410
+ additionalProperties: false,
411
+ description: "Edge metadata for a Check\u2194Observable association.",
412
+ properties: {
413
+ observable_key: {
414
+ title: "Observable Key",
415
+ type: "string"
416
+ },
417
+ propagation_mode: {
418
+ $ref: "#/$defs/PropagationMode",
419
+ default: "LOCAL_ONLY"
420
+ }
421
+ },
422
+ required: [
423
+ "observable_key"
424
+ ],
425
+ title: "ObservableLink",
426
+ type: "object"
427
+ },
428
+ PropagationMode: {
429
+ description: "Controls how a Check\u2194Observable link propagates across merged investigations.",
430
+ enum: [
431
+ "LOCAL_ONLY",
432
+ "GLOBAL"
433
+ ],
434
+ title: "PropagationMode",
435
+ type: "string"
436
+ },
314
437
  Relationship: {
315
438
  description: "Represents a relationship between observables.",
316
439
  properties: {
@@ -530,6 +653,11 @@ var cyvest_schema_default = {
530
653
  key: {
531
654
  title: "Key",
532
655
  type: "string"
656
+ },
657
+ score_display: {
658
+ readOnly: true,
659
+ title: "Score Display",
660
+ type: "string"
533
661
  }
534
662
  },
535
663
  required: [
@@ -540,7 +668,8 @@ var cyvest_schema_default = {
540
668
  "score",
541
669
  "level",
542
670
  "taxonomies",
543
- "key"
671
+ "key",
672
+ "score_display"
544
673
  ],
545
674
  title: "ThreatIntel",
546
675
  type: "object"
@@ -551,6 +680,24 @@ var cyvest_schema_default = {
551
680
  additionalProperties: false,
552
681
  description: "Schema for a complete serialized investigation.\n\nThis model describes the output of `serialize_investigation()` from\n`cyvest.io_serialization`. It is the top-level schema for exported investigations.\n\nEntity types reference the runtime models directly. When generating schemas with\n`mode='serialization'`, Pydantic respects field_serializer decorators and produces\nschemas matching the actual model_dump() output.",
553
682
  properties: {
683
+ investigation_id: {
684
+ description: "Stable investigation identity (ULID).",
685
+ title: "Investigation Id",
686
+ type: "string"
687
+ },
688
+ investigation_name: {
689
+ anyOf: [
690
+ {
691
+ type: "string"
692
+ },
693
+ {
694
+ type: "null"
695
+ }
696
+ ],
697
+ default: null,
698
+ description: "Optional human-readable investigation name.",
699
+ title: "Investigation Name"
700
+ },
554
701
  started_at: {
555
702
  description: "Investigation start time (UTC).",
556
703
  format: "date-time",
@@ -579,6 +726,14 @@ var cyvest_schema_default = {
579
726
  title: "Whitelists",
580
727
  type: "array"
581
728
  },
729
+ event_log: {
730
+ description: "Append-only investigation audit log.",
731
+ items: {
732
+ $ref: "#/$defs/AuditEvent"
733
+ },
734
+ title: "Event Log",
735
+ type: "array"
736
+ },
582
737
  observables: {
583
738
  additionalProperties: {
584
739
  $ref: "#/$defs/Observable"
@@ -644,9 +799,16 @@ var cyvest_schema_default = {
644
799
  data_extraction: {
645
800
  $ref: "#/$defs/DataExtractionSchema",
646
801
  description: "Data extraction metadata."
802
+ },
803
+ score_display: {
804
+ description: "Global investigation score formatted as fixed-point x.xx.",
805
+ readOnly: true,
806
+ title: "Score Display",
807
+ type: "string"
647
808
  }
648
809
  },
649
810
  required: [
811
+ "investigation_id",
650
812
  "started_at",
651
813
  "score",
652
814
  "level",
@@ -660,7 +822,8 @@ var cyvest_schema_default = {
660
822
  "containers",
661
823
  "stats",
662
824
  "stats_checks",
663
- "data_extraction"
825
+ "data_extraction",
826
+ "score_display"
664
827
  ],
665
828
  title: "Cyvest Investigation",
666
829
  type: "object"
@@ -1140,17 +1303,6 @@ function findChecksByCheckId(inv, checkId) {
1140
1303
  }
1141
1304
  return result;
1142
1305
  }
1143
- function findManuallyScored(inv) {
1144
- const result = [];
1145
- for (const checks of Object.values(inv.checks)) {
1146
- for (const check of checks) {
1147
- if (check.score_policy === "manual") {
1148
- result.push(check);
1149
- }
1150
- }
1151
- }
1152
- return result;
1153
- }
1154
1306
  function findThreatIntelBySource(inv, source) {
1155
1307
  const normalizedSource = source.trim().toLowerCase();
1156
1308
  return Object.values(inv.threat_intels).filter(
@@ -1193,13 +1345,32 @@ function findContainersAtLeast(inv, minLevel2) {
1193
1345
  }
1194
1346
  function getChecksForObservable(inv, observableKey) {
1195
1347
  const result = [];
1348
+ const seen = /* @__PURE__ */ new Set();
1349
+ const checkLookup = /* @__PURE__ */ new Map();
1196
1350
  for (const checks of Object.values(inv.checks)) {
1197
1351
  for (const check of checks) {
1198
- if (check.observables.includes(observableKey)) {
1352
+ checkLookup.set(check.key, check);
1353
+ }
1354
+ }
1355
+ const observable = inv.observables[observableKey];
1356
+ if (observable) {
1357
+ for (const checkKey of observable.check_links) {
1358
+ const check = checkLookup.get(checkKey);
1359
+ if (check && !seen.has(check.key)) {
1199
1360
  result.push(check);
1361
+ seen.add(check.key);
1200
1362
  }
1201
1363
  }
1202
1364
  }
1365
+ for (const check of checkLookup.values()) {
1366
+ if (seen.has(check.key)) {
1367
+ continue;
1368
+ }
1369
+ if (check.observable_links.some((link) => link.observable_key === observableKey)) {
1370
+ result.push(check);
1371
+ seen.add(check.key);
1372
+ }
1373
+ }
1203
1374
  return result;
1204
1375
  }
1205
1376
  function getThreatIntelsForObservable(inv, observableKey) {
@@ -1215,7 +1386,11 @@ function getObservablesForCheck(inv, checkKey) {
1215
1386
  for (const checks of Object.values(inv.checks)) {
1216
1387
  for (const check of checks) {
1217
1388
  if (check.key === checkKey) {
1218
- return check.observables.map((obsKey) => inv.observables[obsKey]).filter((obs) => obs !== void 0);
1389
+ const keys = /* @__PURE__ */ new Set();
1390
+ for (const link of check.observable_links) {
1391
+ keys.add(link.observable_key);
1392
+ }
1393
+ return Array.from(keys).map((obsKey) => inv.observables[obsKey]).filter((obs) => obs !== void 0);
1219
1394
  }
1220
1395
  }
1221
1396
  }
@@ -1587,7 +1762,6 @@ export {
1587
1762
  findExternalObservables,
1588
1763
  findInternalObservables,
1589
1764
  findLeafObservables,
1590
- findManuallyScored,
1591
1765
  findObservablesAtLeast,
1592
1766
  findObservablesByLevel,
1593
1767
  findObservablesByType,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyvest/cyvest-js",
3
- "version": "3.1.0",
3
+ "version": "4.0.0",
4
4
  "main": "dist/index.cjs",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",
@@ -10,10 +10,10 @@
10
10
  "ajv-formats": "^3.0.1"
11
11
  },
12
12
  "devDependencies": {
13
- "json-schema-to-typescript": "^13.1.1",
14
- "tsup": "^8.0.0",
15
- "typescript": "^5.6.0",
16
- "vitest": "^2.0.0"
13
+ "json-schema-to-typescript": "^15.0.4",
14
+ "tsup": "^8.5.1",
15
+ "typescript": "^5.9.3",
16
+ "vitest": "^4.0.16"
17
17
  },
18
18
  "engines": {
19
19
  "node": ">=20"
package/src/finders.ts CHANGED
@@ -288,24 +288,6 @@ export function findChecksByCheckId(
288
288
  return result;
289
289
  }
290
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
291
  // ============================================================================
310
292
  // Threat Intel Finders
311
293
  // ============================================================================
@@ -434,15 +416,37 @@ export function getChecksForObservable(
434
416
  observableKey: string
435
417
  ): Check[] {
436
418
  const result: Check[] = [];
419
+ const seen = new Set<string>();
420
+ const checkLookup = new Map<string, Check>();
437
421
 
438
422
  for (const checks of Object.values(inv.checks)) {
439
423
  for (const check of checks) {
440
- if (check.observables.includes(observableKey)) {
424
+ checkLookup.set(check.key, check);
425
+ }
426
+ }
427
+
428
+ const observable = inv.observables[observableKey];
429
+ if (observable) {
430
+ for (const checkKey of observable.check_links) {
431
+ const check = checkLookup.get(checkKey);
432
+ if (check && !seen.has(check.key)) {
441
433
  result.push(check);
434
+ seen.add(check.key);
442
435
  }
443
436
  }
444
437
  }
445
438
 
439
+ for (const check of checkLookup.values()) {
440
+ if (seen.has(check.key)) {
441
+ continue;
442
+ }
443
+
444
+ if (check.observable_links.some((link) => link.observable_key === observableKey)) {
445
+ result.push(check);
446
+ seen.add(check.key);
447
+ }
448
+ }
449
+
446
450
  return result;
447
451
  }
448
452
 
@@ -486,7 +490,12 @@ export function getObservablesForCheck(
486
490
  for (const checks of Object.values(inv.checks)) {
487
491
  for (const check of checks) {
488
492
  if (check.key === checkKey) {
489
- return check.observables
493
+ const keys = new Set<string>();
494
+ for (const link of check.observable_links) {
495
+ keys.add(link.observable_key);
496
+ }
497
+
498
+ return Array.from(keys)
490
499
  .map((obsKey) => inv.observables[obsKey])
491
500
  .filter((obs): obs is Observable => obs !== undefined);
492
501
  }