@cyvest/cyvest-js 3.2.0 → 4.1.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,17 +127,17 @@ 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"
@@ -65,22 +156,14 @@ var cyvest_schema_default = {
65
156
  "extra",
66
157
  "score",
67
158
  "level",
68
- "observables",
159
+ "origin_investigation_id",
160
+ "observable_links",
69
161
  "key",
70
162
  "score_display"
71
163
  ],
72
164
  title: "Check",
73
165
  type: "object"
74
166
  },
75
- CheckScorePolicy: {
76
- description: "Controls how a check reacts to linked observables.",
77
- enum: [
78
- "auto",
79
- "manual"
80
- ],
81
- title: "CheckScorePolicy",
82
- type: "string"
83
- },
84
167
  Container: {
85
168
  additionalProperties: false,
86
169
  description: "Groups checks and sub-containers for hierarchical organization.\n\nContainers allow structuring the investigation into logical sections\nwith aggregated scores and levels.",
@@ -141,6 +224,10 @@ var cyvest_schema_default = {
141
224
  root_type: {
142
225
  anyOf: [
143
226
  {
227
+ enum: [
228
+ "file",
229
+ "artifact"
230
+ ],
144
231
  type: "string"
145
232
  },
146
233
  {
@@ -151,13 +238,13 @@ var cyvest_schema_default = {
151
238
  description: "Root observable type used during data extraction.",
152
239
  title: "Root Type"
153
240
  },
154
- score_mode: {
241
+ score_mode_obs: {
155
242
  $ref: "#/$defs/ScoreMode",
156
- description: "Score aggregation mode: 'max' takes highest score, 'sum' adds all scores."
243
+ description: "Observable score aggregation mode: 'max' takes highest score, 'sum' adds all scores."
157
244
  }
158
245
  },
159
246
  required: [
160
- "score_mode"
247
+ "score_mode_obs"
161
248
  ],
162
249
  title: "DataExtractionSchema",
163
250
  type: "object"
@@ -290,13 +377,13 @@ var cyvest_schema_default = {
290
377
  title: "Key",
291
378
  type: "string"
292
379
  },
293
- generated_by_checks: {
294
- description: "Checks that generated this observable.",
380
+ check_links: {
381
+ description: "Checks that currently link to this observable (navigation-only).",
295
382
  items: {
296
383
  type: "string"
297
384
  },
298
385
  readOnly: true,
299
- title: "Generated By Checks",
386
+ title: "Check Links",
300
387
  type: "array"
301
388
  },
302
389
  score_display: {
@@ -317,12 +404,40 @@ var cyvest_schema_default = {
317
404
  "threat_intels",
318
405
  "relationships",
319
406
  "key",
320
- "generated_by_checks",
407
+ "check_links",
321
408
  "score_display"
322
409
  ],
323
410
  title: "Observable",
324
411
  type: "object"
325
412
  },
413
+ ObservableLink: {
414
+ additionalProperties: false,
415
+ description: "Edge metadata for a Check\u2194Observable association.",
416
+ properties: {
417
+ observable_key: {
418
+ title: "Observable Key",
419
+ type: "string"
420
+ },
421
+ propagation_mode: {
422
+ $ref: "#/$defs/PropagationMode",
423
+ default: "LOCAL_ONLY"
424
+ }
425
+ },
426
+ required: [
427
+ "observable_key"
428
+ ],
429
+ title: "ObservableLink",
430
+ type: "object"
431
+ },
432
+ PropagationMode: {
433
+ description: "Controls how a Check\u2194Observable link propagates across merged investigations.",
434
+ enum: [
435
+ "LOCAL_ONLY",
436
+ "GLOBAL"
437
+ ],
438
+ title: "PropagationMode",
439
+ type: "string"
440
+ },
326
441
  Relationship: {
327
442
  description: "Represents a relationship between observables.",
328
443
  properties: {
@@ -482,28 +597,6 @@ var cyvest_schema_default = {
482
597
  title: "StatisticsSchema",
483
598
  type: "object"
484
599
  },
485
- StatsChecksSchema: {
486
- additionalProperties: false,
487
- description: "Schema for check statistics summary.",
488
- properties: {
489
- checks: {
490
- minimum: 0,
491
- title: "Checks",
492
- type: "integer"
493
- },
494
- applied: {
495
- minimum: 0,
496
- title: "Applied",
497
- type: "integer"
498
- }
499
- },
500
- required: [
501
- "checks",
502
- "applied"
503
- ],
504
- title: "StatsChecksSchema",
505
- type: "object"
506
- },
507
600
  ThreatIntel: {
508
601
  description: "Represents threat intelligence from an external source.\n\nThreat intelligence provides verdicts about observables from sources\nlike VirusTotal, URLScan.io, etc.",
509
602
  properties: {
@@ -569,6 +662,24 @@ var cyvest_schema_default = {
569
662
  additionalProperties: false,
570
663
  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.",
571
664
  properties: {
665
+ investigation_id: {
666
+ description: "Stable investigation identity (ULID).",
667
+ title: "Investigation Id",
668
+ type: "string"
669
+ },
670
+ investigation_name: {
671
+ anyOf: [
672
+ {
673
+ type: "string"
674
+ },
675
+ {
676
+ type: "null"
677
+ }
678
+ ],
679
+ default: null,
680
+ description: "Optional human-readable investigation name.",
681
+ title: "Investigation Name"
682
+ },
572
683
  started_at: {
573
684
  description: "Investigation start time (UTC).",
574
685
  format: "date-time",
@@ -597,6 +708,14 @@ var cyvest_schema_default = {
597
708
  title: "Whitelists",
598
709
  type: "array"
599
710
  },
711
+ event_log: {
712
+ description: "Append-only investigation audit log.",
713
+ items: {
714
+ $ref: "#/$defs/AuditEvent"
715
+ },
716
+ title: "Event Log",
717
+ type: "array"
718
+ },
600
719
  observables: {
601
720
  additionalProperties: {
602
721
  $ref: "#/$defs/Observable"
@@ -655,10 +774,6 @@ var cyvest_schema_default = {
655
774
  $ref: "#/$defs/StatisticsSchema",
656
775
  description: "Investigation statistics summary."
657
776
  },
658
- stats_checks: {
659
- $ref: "#/$defs/StatsChecksSchema",
660
- description: "Check statistics summary."
661
- },
662
777
  data_extraction: {
663
778
  $ref: "#/$defs/DataExtractionSchema",
664
779
  description: "Data extraction metadata."
@@ -671,6 +786,7 @@ var cyvest_schema_default = {
671
786
  }
672
787
  },
673
788
  required: [
789
+ "investigation_id",
674
790
  "started_at",
675
791
  "score",
676
792
  "level",
@@ -683,7 +799,6 @@ var cyvest_schema_default = {
683
799
  "enrichments",
684
800
  "containers",
685
801
  "stats",
686
- "stats_checks",
687
802
  "data_extraction",
688
803
  "score_display"
689
804
  ],
@@ -1056,9 +1171,6 @@ function getWhitelists(inv) {
1056
1171
  function getStats(inv) {
1057
1172
  return inv.stats;
1058
1173
  }
1059
- function getStatsChecks(inv) {
1060
- return inv.stats_checks;
1061
- }
1062
1174
  function getDataExtraction(inv) {
1063
1175
  return inv.data_extraction;
1064
1176
  }
@@ -1165,17 +1277,6 @@ function findChecksByCheckId(inv, checkId) {
1165
1277
  }
1166
1278
  return result;
1167
1279
  }
1168
- function findManuallyScored(inv) {
1169
- const result = [];
1170
- for (const checks of Object.values(inv.checks)) {
1171
- for (const check of checks) {
1172
- if (check.score_policy === "manual") {
1173
- result.push(check);
1174
- }
1175
- }
1176
- }
1177
- return result;
1178
- }
1179
1280
  function findThreatIntelBySource(inv, source) {
1180
1281
  const normalizedSource = source.trim().toLowerCase();
1181
1282
  return Object.values(inv.threat_intels).filter(
@@ -1218,13 +1319,32 @@ function findContainersAtLeast(inv, minLevel2) {
1218
1319
  }
1219
1320
  function getChecksForObservable(inv, observableKey) {
1220
1321
  const result = [];
1322
+ const seen = /* @__PURE__ */ new Set();
1323
+ const checkLookup = /* @__PURE__ */ new Map();
1221
1324
  for (const checks of Object.values(inv.checks)) {
1222
1325
  for (const check of checks) {
1223
- if (check.observables.includes(observableKey)) {
1326
+ checkLookup.set(check.key, check);
1327
+ }
1328
+ }
1329
+ const observable = inv.observables[observableKey];
1330
+ if (observable) {
1331
+ for (const checkKey of observable.check_links) {
1332
+ const check = checkLookup.get(checkKey);
1333
+ if (check && !seen.has(check.key)) {
1224
1334
  result.push(check);
1335
+ seen.add(check.key);
1225
1336
  }
1226
1337
  }
1227
1338
  }
1339
+ for (const check of checkLookup.values()) {
1340
+ if (seen.has(check.key)) {
1341
+ continue;
1342
+ }
1343
+ if (check.observable_links.some((link) => link.observable_key === observableKey)) {
1344
+ result.push(check);
1345
+ seen.add(check.key);
1346
+ }
1347
+ }
1228
1348
  return result;
1229
1349
  }
1230
1350
  function getThreatIntelsForObservable(inv, observableKey) {
@@ -1240,7 +1360,11 @@ function getObservablesForCheck(inv, checkKey) {
1240
1360
  for (const checks of Object.values(inv.checks)) {
1241
1361
  for (const check of checks) {
1242
1362
  if (check.key === checkKey) {
1243
- return check.observables.map((obsKey) => inv.observables[obsKey]).filter((obs) => obs !== void 0);
1363
+ const keys = /* @__PURE__ */ new Set();
1364
+ for (const link of check.observable_links) {
1365
+ keys.add(link.observable_key);
1366
+ }
1367
+ return Array.from(keys).map((obsKey) => inv.observables[obsKey]).filter((obs) => obs !== void 0);
1244
1368
  }
1245
1369
  }
1246
1370
  }
@@ -1612,7 +1736,6 @@ export {
1612
1736
  findExternalObservables,
1613
1737
  findInternalObservables,
1614
1738
  findLeafObservables,
1615
- findManuallyScored,
1616
1739
  findObservablesAtLeast,
1617
1740
  findObservablesByLevel,
1618
1741
  findObservablesByType,
@@ -1671,7 +1794,6 @@ export {
1671
1794
  getRelatedObservablesByType,
1672
1795
  getRelationshipsForObservable,
1673
1796
  getStats,
1674
- getStatsChecks,
1675
1797
  getSuspiciousChecks,
1676
1798
  getSuspiciousObservables,
1677
1799
  getThreatIntel,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyvest/cyvest-js",
3
- "version": "3.2.0",
3
+ "version": "4.1.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
  }
package/src/getters.ts CHANGED
@@ -359,16 +359,6 @@ export function getStats(inv: CyvestInvestigation) {
359
359
  return inv.stats;
360
360
  }
361
361
 
362
- /**
363
- * Get the investigation check statistics.
364
- *
365
- * @param inv - The investigation
366
- * @returns Check statistics object
367
- */
368
- export function getStatsChecks(inv: CyvestInvestigation) {
369
- return inv.stats_checks;
370
- }
371
-
372
362
  /**
373
363
  * Get the data extraction configuration.
374
364
  *
@@ -1,5 +1,9 @@
1
1
  // AUTO-GENERATED FROM cyvest.schema.json — DO NOT EDIT
2
2
 
3
+ /**
4
+ * Optional human-readable investigation name.
5
+ */
6
+ export type InvestigationName = string | null;
3
7
  /**
4
8
  * Security level classification for checks, observables, and threat intelligence.
5
9
  *
@@ -11,6 +15,15 @@ export type Justification = string | null;
11
15
  * List of whitelist entries applied to this investigation.
12
16
  */
13
17
  export type Whitelists = InvestigationWhitelist[];
18
+ export type Actor = string | null;
19
+ export type Reason = string | null;
20
+ export type Tool = string | null;
21
+ export type ObjectType = string | null;
22
+ export type ObjectKey = string | null;
23
+ /**
24
+ * Append-only investigation audit log.
25
+ */
26
+ export type EventLog = AuditEvent[];
14
27
  export type ThreatIntels = string[];
15
28
  /**
16
29
  * Direction of a relationship between observables.
@@ -18,14 +31,14 @@ export type ThreatIntels = string[];
18
31
  export type RelationshipDirection = "outbound" | "inbound" | "bidirectional";
19
32
  export type Relationships = Relationship[];
20
33
  /**
21
- * Checks that generated this observable.
34
+ * Checks that currently link to this observable (navigation-only).
22
35
  */
23
- export type GeneratedByChecks = string[];
24
- export type Observables1 = string[];
36
+ export type CheckLinks = string[];
25
37
  /**
26
- * Controls how a check reacts to linked observables.
38
+ * Controls how a Check↔Observable link propagates across merged investigations.
27
39
  */
28
- export type CheckScorePolicy = "auto" | "manual";
40
+ export type PropagationMode = "LOCAL_ONLY" | "GLOBAL";
41
+ export type ObservableLinks = ObservableLink[];
29
42
  export type Taxonomies = {
30
43
  [k: string]: unknown;
31
44
  }[];
@@ -33,7 +46,7 @@ export type Checks1 = string[];
33
46
  /**
34
47
  * Root observable type used during data extraction.
35
48
  */
36
- export type RootType = string | null;
49
+ export type RootType = ("file" | "artifact") | null;
37
50
  /**
38
51
  * Score calculation mode for observables.
39
52
  */
@@ -50,6 +63,11 @@ export type ScoreMode = "max" | "sum";
50
63
  * schemas matching the actual model_dump() output.
51
64
  */
52
65
  export interface CyvestInvestigation {
66
+ /**
67
+ * Stable investigation identity (ULID).
68
+ */
69
+ investigation_id: string;
70
+ investigation_name?: InvestigationName;
53
71
  /**
54
72
  * Investigation start time (UTC).
55
73
  */
@@ -64,6 +82,7 @@ export interface CyvestInvestigation {
64
82
  */
65
83
  whitelisted: boolean;
66
84
  whitelists: Whitelists;
85
+ event_log?: EventLog;
67
86
  observables: Observables;
68
87
  checks: Checks;
69
88
  checks_by_level: ChecksByLevel;
@@ -71,7 +90,6 @@ export interface CyvestInvestigation {
71
90
  enrichments: Enrichments;
72
91
  containers: Containers;
73
92
  stats: StatisticsSchema;
74
- stats_checks: StatsChecksSchema;
75
93
  data_extraction: DataExtractionSchema;
76
94
  /**
77
95
  * Global investigation score formatted as fixed-point x.xx.
@@ -87,6 +105,24 @@ export interface InvestigationWhitelist {
87
105
  justification?: Justification;
88
106
  [k: string]: unknown;
89
107
  }
108
+ /**
109
+ * Centralized audit event for investigation-level changes.
110
+ */
111
+ export interface AuditEvent {
112
+ event_id: string;
113
+ timestamp: string;
114
+ event_type: string;
115
+ actor?: Actor;
116
+ reason?: Reason;
117
+ tool?: Tool;
118
+ object_type?: ObjectType;
119
+ object_key?: ObjectKey;
120
+ details?: Details;
121
+ [k: string]: unknown;
122
+ }
123
+ export interface Details {
124
+ [k: string]: unknown;
125
+ }
90
126
  /**
91
127
  * Observables keyed by their unique key.
92
128
  */
@@ -111,7 +147,7 @@ export interface Observable {
111
147
  threat_intels: ThreatIntels;
112
148
  relationships: Relationships;
113
149
  key: string;
114
- generated_by_checks: GeneratedByChecks;
150
+ check_links: CheckLinks;
115
151
  score_display: string;
116
152
  [k: string]: unknown;
117
153
  }
@@ -147,8 +183,8 @@ export interface Check {
147
183
  extra: Extra1;
148
184
  score: number;
149
185
  level: Level;
150
- observables: Observables1;
151
- score_policy?: CheckScorePolicy;
186
+ origin_investigation_id: string;
187
+ observable_links: ObservableLinks;
152
188
  key: string;
153
189
  score_display: string;
154
190
  [k: string]: unknown;
@@ -156,6 +192,13 @@ export interface Check {
156
192
  export interface Extra1 {
157
193
  [k: string]: unknown;
158
194
  }
195
+ /**
196
+ * Edge metadata for a Check↔Observable association.
197
+ */
198
+ export interface ObservableLink {
199
+ observable_key: string;
200
+ propagation_mode?: PropagationMode;
201
+ }
159
202
  /**
160
203
  * Check keys organized by level name.
161
204
  */
@@ -280,17 +323,10 @@ export interface ThreatIntelBySource {
280
323
  export interface ThreatIntelByLevel {
281
324
  [k: string]: number;
282
325
  }
283
- /**
284
- * Schema for check statistics summary.
285
- */
286
- export interface StatsChecksSchema {
287
- checks: number;
288
- applied: number;
289
- }
290
326
  /**
291
327
  * Schema for data extraction metadata.
292
328
  */
293
329
  export interface DataExtractionSchema {
294
330
  root_type?: RootType;
295
- score_mode: ScoreMode;
331
+ score_mode_obs: ScoreMode;
296
332
  }