cyvest 4.4.0__py3-none-any.whl → 5.1.0__py3-none-any.whl

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.

Potentially problematic release.


This version of cyvest might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: cyvest
3
- Version: 4.4.0
3
+ Version: 5.1.0
4
4
  Summary: Cybersecurity investigation model
5
5
  Keywords: cybersecurity,investigation,threat-intel,security-analysis
6
6
  Author: PakitoSec
@@ -47,6 +47,7 @@ Description-Content-Type: text/markdown
47
47
  - 💾 **Multiple Export Formats**: JSON and Markdown output for reporting and LLM consumption
48
48
  - 🎨 **Rich Console Output**: Beautiful terminal displays with the Rich library
49
49
  - 🧩 **Fluent helpers**: Convenient API with method chaining for rapid development
50
+ - 🔬 **Investigation Comparison**: Compare investigations with tolerance rules and visual diff output
50
51
 
51
52
  ## Installation
52
53
 
@@ -111,14 +112,14 @@ cv.io_save_json("investigation.json")
111
112
  ### Model Proxies
112
113
 
113
114
  Cyvest only exposes immutable model proxies. Helpers like `observable_create`, `check_create`, and the
114
- fluent `cv.observable()`/`cv.check()` convenience methods return `ObservableProxy`, `CheckProxy`, `ContainerProxy`, etc.
115
+ fluent `cv.observable()`/`cv.check()` convenience methods return `ObservableProxy`, `CheckProxy`, `TagProxy`, etc.
115
116
  These proxies reflect the live investigation state but raise `AttributeError` if you try to assign to their attributes.
116
117
  All mutations are routed through the Investigation layer, so use the facade helpers (`cv.observable_set_level`,
117
118
  `cv.check_update_score`, `cv.observable_add_threat_intel`) or the built-in fluent methods on the proxies themselves
118
119
  (`with_ti`, `relate_to`, `link_observable`, `with_score`, …) so the score engine and audit log stay consistent.
119
120
 
120
121
  Mutation helpers that reference existing objects (for example, `cv.observable_add_relationship`,
121
- `cv.check_link_observable`, `cv.container_add_check`) raise `KeyError` when a key is missing.
122
+ `cv.check_link_observable`, `cv.tag_add_check`) raise `KeyError` when a key is missing.
122
123
 
123
124
  Safe metadata fields like `comment`, `extra`, or `internal` can be updated through the proxies without breaking score
124
125
  consistency:
@@ -208,15 +209,22 @@ ti.add_taxonomy(level=cv.LVL.SUSPICIOUS, name="confidence", value="medium")
208
209
  ti.remove_taxonomy("confidence")
209
210
  ```
210
211
 
211
- ### Containers
212
+ ### Tags
212
213
 
213
- Containers organize checks hierarchically:
214
+ Tags organize checks with automatic hierarchy based on `:` delimiter:
214
215
 
215
216
  ```python
216
- with cv.container("network_analysis") as network:
217
- with network.sub_container("c2_detection") as c2:
218
- check = cv.check("beacon_detection", "network", "Detect C2 beacons")
219
- c2.add_check(check)
217
+ # Simple: pass tag names directly (auto-creates tags)
218
+ check = cv.check("beacon_detection", "Detect C2 beacons")
219
+ check.tagged("network", "c2:detection", "suspicious")
220
+
221
+ # With description: create tag first, then reference it
222
+ tag = cv.tag("network:c2:detection", "C2 Detection Checks")
223
+ check.tagged(tag)
224
+
225
+ # Query hierarchy
226
+ children = cv.tag_get_children("network") # ["network:c2"]
227
+ descendants = cv.tag_get_descendants("network") # ["network:c2", "network:c2:detection"]
220
228
  ```
221
229
 
222
230
  ### Lookup Helpers
@@ -232,9 +240,9 @@ check = cv.check_create("malware_detection", "endpoint", "Verify file hash")
232
240
  same_check = cv.check_get("malware_detection", "endpoint")
233
241
  same_check_by_key = cv.check_get(check.key)
234
242
 
235
- container = cv.container_create("network_analysis")
236
- same_container = cv.container_get("network_analysis")
237
- same_container_by_key = cv.container_get(container.key)
243
+ tag = cv.tag_create("network:analysis")
244
+ same_tag = cv.tag_get("network:analysis")
245
+ same_tag_by_key = cv.tag_get(tag.key)
238
246
 
239
247
  enrichment = cv.enrichment_create("whois", {"registrar": "Example Inc"})
240
248
  same_enrichment = cv.enrichment_get("whois")
@@ -383,6 +391,88 @@ Its key is derived from type + value (e.g. `obs:file:root` or `obs:artifact:root
383
391
 
384
392
  This design enables flexible investigation structures while preventing unintended score contamination.
385
393
 
394
+ ### Comparing Investigations
395
+
396
+ Compare two investigations to identify differences in checks, observables, and threat intelligence:
397
+
398
+ ```python
399
+ from decimal import Decimal
400
+ from cyvest import Cyvest, ExpectedResult, Level, compare_investigations
401
+ from cyvest.io_rich import display_diff
402
+
403
+ # Create expected and actual investigations
404
+ expected = Cyvest(investigation_name="expected")
405
+ expected.check_create("domain-check", "Verify domain", score=Decimal("1.0"))
406
+
407
+ actual = Cyvest(investigation_name="actual")
408
+ actual.check_create("domain-check", "Verify domain", score=Decimal("2.0"))
409
+ actual.check_create("new-check", "New detection", score=Decimal("1.5"))
410
+
411
+ # Compare investigations
412
+ diffs = compare_investigations(actual, expected)
413
+ # diffs contains:
414
+ # - MISMATCH for domain-check (score changed 1.0 -> 2.0)
415
+ # - ADDED for new-check
416
+ ```
417
+
418
+ **Tolerance Rules**
419
+
420
+ Use `result_expected` rules to define acceptable score variations:
421
+
422
+ ```python
423
+ # Define tolerance rules
424
+ rules = [
425
+ # Accept any score >= 1.0 for this check
426
+ ExpectedResult(check_name="domain-check", score=">= 1.0"),
427
+ # Accept any score < 3.0 for roger-ai
428
+ ExpectedResult(key="chk:roger-ai", level=Level.SUSPICIOUS, score="< 3.0"),
429
+ ]
430
+
431
+ # Compare with tolerance - checks satisfying rules are not flagged as diffs
432
+ diffs = compare_investigations(actual, expected, result_expected=rules)
433
+ ```
434
+
435
+ Supported operators: `>=`, `<=`, `>`, `<`, `==`, `!=`
436
+
437
+ **Visual Diff Display**
438
+
439
+ Display differences in a rich table format:
440
+
441
+ ```python
442
+ from cyvest.io_rich import display_diff
443
+ from logurich import logger
444
+
445
+ # Display diff table with tree structure showing observables and threat intel
446
+ display_diff(diffs, lambda r: logger.rich("INFO", r), title="Investigation Diff")
447
+ ```
448
+
449
+ Output:
450
+ ```
451
+ ╭────────────────────────────────────────────────┬────────────────────┬─────────────────┬────────╮
452
+ │ Key │ Expected │ Actual │ Status │
453
+ ├────────────────────────────────────────────────┼────────────────────┼─────────────────┼────────┤
454
+ │ chk:new-check │ - │ NOTABLE 1.50 │ + │
455
+ │ └── domain: example.com │ - │ INFO 0.00 │ │
456
+ │ └── VirusTotal │ - │ INFO 0.00 │ │
457
+ ├────────────────────────────────────────────────┼────────────────────┼─────────────────┼────────┤
458
+ │ chk:domain-check │ NOTABLE 1.00 │ NOTABLE 2.00 │ ✗ │
459
+ ╰────────────────────────────────────────────────┴────────────────────┴─────────────────┴────────╯
460
+ ```
461
+
462
+ Status symbols: `+` (added), `-` (removed), `✗` (mismatch)
463
+
464
+ **Convenience Methods**
465
+
466
+ Use methods directly on Cyvest objects:
467
+
468
+ ```python
469
+ # Compare and get diff items
470
+ diffs = actual.compare(expected=expected, result_expected=rules)
471
+
472
+ # Compare and display in one call
473
+ actual.display_diff(expected=expected, title="My Investigation Diff")
474
+ ```
475
+
386
476
  ## Examples
387
477
 
388
478
  See the `examples/` directory for complete examples:
@@ -392,6 +482,7 @@ See the `examples/` directory for complete examples:
392
482
  - **03_merge_demo.py**: Multi-process investigation merging
393
483
  - **04_email.py**: Multi-threaded investigation with SharedInvestigationContext
394
484
  - **05_visualization.py**: Interactive HTML visualization showcasing scores, levels, and relationship flows
485
+ - **06_compare_investigations.py**: Compare investigations with tolerance rules and visual diff output
395
486
 
396
487
  Run an example:
397
488
 
@@ -522,6 +613,7 @@ Cyvest is designed for:
522
613
  - **Malware Analysis**: Track relationships between artifacts
523
614
  - **Phishing Analysis**: Analyze emails and linked resources
524
615
  - **Integration**: Combine results from multiple security tools
616
+ - **Regression Testing**: Compare investigation outputs across rule or detection updates
525
617
 
526
618
  ## Architecture Highlights
527
619
 
@@ -531,6 +623,7 @@ Cyvest is designed for:
531
623
  - **Score Propagation**: Automatic hierarchical score calculation
532
624
  - **Flexible Export**: JSON for storage, Markdown for LLM analysis
533
625
  - **Audit Trail**: Score change history for debugging
626
+ - **Investigation Comparison**: Compare investigations with tolerance rules for regression testing
534
627
 
535
628
  ## Future Enhancements
536
629
 
@@ -0,0 +1,24 @@
1
+ cyvest/__init__.py,sha256=cqJ-Sbwopy3acHEn1UO4C7wkqde8rE_iuD3SByy5cmU,1383
2
+ cyvest/cli.py,sha256=K_BgHhBEt_lw7CehM3wpDb03BOA2uzPRD2XNRGld0Pc,14321
3
+ cyvest/compare.py,sha256=CcBGP6pF1fp4tYl3mdTJke8dgzr8C4YTJDA9eOvpVKQ,10119
4
+ cyvest/cyvest.py,sha256=vAeh7q9_HQ87vMmJmpSkAwi4zTlRR55-KhjKP7MwJok,47459
5
+ cyvest/investigation.py,sha256=dt4jzx5TjdWCXXvKUdX0eiri10mCS87EKuPPB1dt-Bw,61447
6
+ cyvest/io_rich.py,sha256=fLg-1-SiHbUTMk7rhic425WBdqg06191O9Tu05LEHOg,24813
7
+ cyvest/io_schema.py,sha256=Z-vlhrbWnGJcIm8k-XVSmzv1vbSvl7I6Z2GV55iU1fc,1296
8
+ cyvest/io_serialization.py,sha256=ZNrjY29SvJoeGZHOTajRbdKUqmsayNw1cP-5HeOTvik,17755
9
+ cyvest/io_visualization.py,sha256=kPT8cAqZJ3liSA2BzwvQXMaejnk496m0Ck11QXJCQLc,11010
10
+ cyvest/keys.py,sha256=xNpSZW5VDzeqc5F0-66-jHM7_v00wt2A70kvbqRSxwA,5678
11
+ cyvest/level_score_rules.py,sha256=MoCCYCLsTDCM-OqLV4Ln_GEH7H_tjNpK4tte89IEQeU,2391
12
+ cyvest/levels.py,sha256=1d3YHCbP2aptSGyIwVzLF4rJjAYdpSzmOnzjs7ZLsdg,4556
13
+ cyvest/model.py,sha256=Em3D5kXS5HCUrehjEEOPWXbX1-UVaEf4ebOs5Z1DBAw,18600
14
+ cyvest/model_enums.py,sha256=t7uFDnGcLXLMZdwgHMxk3HU5KHQgqUvBNZ5i_H2_Jvc,2050
15
+ cyvest/model_schema.py,sha256=Xrd10_dRPcXyiotAF6LJHi7iioGqPBvQlCE87vUVvaM,6037
16
+ cyvest/proxies.py,sha256=74FQvC3gDRpCjv7_-G6x90eJnQmcSVom_3jwS_3EGTY,19525
17
+ cyvest/score.py,sha256=CiD-dcjEVr2oLHPJ4MlcQWv5338Tq-Zn4Q7lXIEq9fY,19807
18
+ cyvest/shared.py,sha256=PidiXY3bfB1ph5B0Ah_etyZKkiUa5S7FoNk5eRjnOLw,19375
19
+ cyvest/stats.py,sha256=X2e9BHqJrfaOHNUt6jShQqy2Pdw4DMdGheRD9WGPmS0,9035
20
+ cyvest/ulid.py,sha256=NA3k6hlgMZyfzLEMJEIMqt73CY2V07Bupqk7gemshDM,1069
21
+ cyvest-5.1.0.dist-info/WHEEL,sha256=eycQt0QpYmJMLKpE3X9iDk8R04v2ZF0x82ogq-zP6bQ,79
22
+ cyvest-5.1.0.dist-info/entry_points.txt,sha256=bwtGzs4Eh9i3WEhW4dhxHaYP8qf8qUN4sDnt4xXywUk,44
23
+ cyvest-5.1.0.dist-info/METADATA,sha256=efWG28fBJ9CRGtXOyLF2l34Q9-wF0Z-juNhW9OgNwUw,23156
24
+ cyvest-5.1.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.21
2
+ Generator: uv 0.9.24
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,23 +0,0 @@
1
- cyvest/__init__.py,sha256=ISyIqEKzFg5WvCKWyAa1k6B3-bSdC8mxhWXkIhuiIYA,1046
2
- cyvest/cli.py,sha256=Zj8RVCG4BXFTNQt0Oo2GgwviF1aY5by_69eZE2XOgkg,12271
3
- cyvest/cyvest.py,sha256=82DSGeog6YjEyZagkD5E2e5Vmxk7hSERfhOA_5YCF0I,45041
4
- cyvest/investigation.py,sha256=LKiP3p74m5UnC1QnCZ5WEX6xtxIyrzI-chdd9nE8YLE,61074
5
- cyvest/io_rich.py,sha256=46mPidDNzqo1hgcWxVrEFRNMdto_nEe8vaaxaOwGqFk,21932
6
- cyvest/io_schema.py,sha256=vhreQyKQbVrXFVj1ddIoAuEXc6zGhTVndHZZdDxTd0c,1302
7
- cyvest/io_serialization.py,sha256=V0KAxGZu11t7LA413L7QlJyh5o2lwytQCLllDflH6I4,18478
8
- cyvest/io_visualization.py,sha256=kPT8cAqZJ3liSA2BzwvQXMaejnk496m0Ck11QXJCQLc,11010
9
- cyvest/keys.py,sha256=9S7ELhloRzKrLPlpfRnwYtL7WXbKIsyhteh62mn3j2E,4614
10
- cyvest/level_score_rules.py,sha256=MoCCYCLsTDCM-OqLV4Ln_GEH7H_tjNpK4tte89IEQeU,2391
11
- cyvest/levels.py,sha256=1d3YHCbP2aptSGyIwVzLF4rJjAYdpSzmOnzjs7ZLsdg,4556
12
- cyvest/model.py,sha256=Bi1IRXM2Ca47t5KpJ1vtSnPyeJhHZ1ZnezE4-JkSwv0,18184
13
- cyvest/model_enums.py,sha256=t7uFDnGcLXLMZdwgHMxk3HU5KHQgqUvBNZ5i_H2_Jvc,2050
14
- cyvest/model_schema.py,sha256=LVJ99i-_T6cSJ3N7OJO2F0k1LjpA23S-PKSk6RYzwks,6119
15
- cyvest/proxies.py,sha256=s_vc4KPWvta7saNNR92WHgzn73V-BdOYhZoRJ_0evHw,19581
16
- cyvest/score.py,sha256=CiD-dcjEVr2oLHPJ4MlcQWv5338Tq-Zn4Q7lXIEq9fY,19807
17
- cyvest/shared.py,sha256=217ufZ-sBAMr0CMflBEpfeTSXs2eRlgNIul1fm0l_Qw,19505
18
- cyvest/stats.py,sha256=mBHgLaRPs1R9l775_K3zQKN6ujup5sGzD5o5CQCPSK8,9926
19
- cyvest/ulid.py,sha256=NA3k6hlgMZyfzLEMJEIMqt73CY2V07Bupqk7gemshDM,1069
20
- cyvest-4.4.0.dist-info/WHEEL,sha256=RRVLqVugUmFOqBedBFAmA4bsgFcROUBiSUKlERi0Hcg,79
21
- cyvest-4.4.0.dist-info/entry_points.txt,sha256=bwtGzs4Eh9i3WEhW4dhxHaYP8qf8qUN4sDnt4xXywUk,44
22
- cyvest-4.4.0.dist-info/METADATA,sha256=8PLZvgm-LAEV_fxfDLupzWHcPa-_mqqCT2rcG0gzDps,18718
23
- cyvest-4.4.0.dist-info/RECORD,,