cyvest 1.0.1__tar.gz → 2.0.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: cyvest
3
- Version: 1.0.1
3
+ Version: 2.0.0
4
4
  Summary: Cybersecurity investigation model
5
5
  Keywords: cybersecurity,investigation,threat-intel,security-analysis
6
6
  Author: PakitoSec
@@ -37,8 +37,8 @@ Description-Content-Type: text/markdown
37
37
  - 🔍 **Structured Investigation Modeling**: Model investigations with observables, checks, threat intelligence, and enrichments
38
38
  - 📊 **Automatic Scoring**: Dynamic score calculation and propagation through investigation hierarchy
39
39
  - 🎯 **Level Classification**: Automatic security level assignment (TRUSTED, INFO, SAFE, NOTABLE, SUSPICIOUS, MALICIOUS)
40
- - 🔗 **Relationship Tracking**: STIX2-compliant relationship modeling between observables
41
- - 🏷️ **STIX2 Type Support**: Built-in enums for STIX2 Observable and Relationship types with autocomplete
40
+ - 🔗 **Relationship Tracking**: Lightweight relationship modeling between observables
41
+ - 🏷️ **Typed Helpers**: Built-in enums for observable types and relationships with autocomplete
42
42
  - 📈 **Real-time Statistics**: Live metrics and aggregations throughout the investigation
43
43
  - 🔄 **Investigation Merging**: Combine investigations from multiple threads or processes
44
44
  - 🧵 **Multi-Threading Support**: Advanced thread-safe shared context available via `cyvest.investigation` module
@@ -82,7 +82,7 @@ from cyvest import Cyvest, Level, ObservableType, RelationshipType
82
82
 
83
83
  # Create an investigation
84
84
  with Cyvest(data={"type": "email"}) as cv:
85
- # Create observables with STIX2 types
85
+ # Create observables
86
86
  url = (
87
87
  cv.observable(ObservableType.URL, "https://phishing-site.com", internal=False)
88
88
  .with_ti("virustotal", score=Decimal("8.5"), level=Level.MALICIOUS)
@@ -126,108 +126,30 @@ Dictionary fields merge by default; pass `merge_extra=False` (or `merge_data=Fal
126
126
 
127
127
  ### Observables
128
128
 
129
- Observables represent cyber artifacts (URLs, IPs, domains, hashes, files, etc.). Use STIX2-compliant types for standardization:
129
+ Observables represent cyber artifacts (URLs, IPs, domains, hashes, files, etc.).
130
130
 
131
131
  ```python
132
- from cyvest import ObservableType, RelationshipType
132
+ from cyvest import ObservableType, RelationshipType, RelationshipDirection
133
133
 
134
- # Create observable with STIX2 type enum
135
134
  url_obs = cv.observable_create(
136
135
  ObservableType.URL,
137
136
  "https://malicious.com",
138
137
  internal=False
139
138
  )
140
139
 
141
- # Or use strings (backward compatible)
142
140
  ip_obs = cv.observable_create("ipv4-addr", "192.0.2.1", internal=False)
143
141
 
144
- # Add threat intelligence
145
- cv.observable_add_threat_intel(
146
- url_obs.key,
147
- source="virustotal",
148
- score=Decimal("9.0"),
149
- level=Level.MALICIOUS,
150
- comment="Detected as malware distribution site"
151
- )
152
-
153
- # Create relationships with STIX2 relationship types
154
- # Accepts observable proxies or string keys
155
142
  cv.observable_add_relationship(
156
143
  url_obs, # Can pass ObservableProxy directly
157
144
  ip_obs, # Or use .key for string keys
158
- RelationshipType.RESOLVES_TO
145
+ RelationshipType.RELATED_TO,
146
+ RelationshipDirection.BIDIRECTIONAL,
159
147
  )
160
148
  ```
161
149
 
162
- **Available STIX2 Observable Types:**
163
-
164
- - Network: `IPV4_ADDR`, `IPV6_ADDR`, `DOMAIN_NAME`, `URL`, `MAC_ADDR`, `NETWORK_TRAFFIC`
165
- - Email: `EMAIL_ADDR`, `EMAIL_MESSAGE`, `EMAIL_MIME_PART`
166
- - File: `FILE`, `DIRECTORY`, `ARTIFACT`
167
- - System: `PROCESS`, `SOFTWARE`, `USER_ACCOUNT`, `WINDOWS_REGISTRY_KEY`
168
- - Other: `AUTONOMOUS_SYSTEM`, `MUTEX`, `X509_CERTIFICATE`
169
-
170
- **Available STIX2 Relationship Types:**
171
-
172
- - Network: `RESOLVES_TO`, `BELONGS_TO`, `COMMUNICATES_WITH`
173
- - File: `CONTAINS`, `DOWNLOADED`, `DROPPED`
174
- - Email: `FROM`, `SENDER`, `TO`, `CC`, `BCC`
175
- - Process: `CREATED`, `OPENED`, `PARENT`, `CHILD`
176
- - General: `RELATED_TO`, `DERIVED_FROM`, `DUPLICATE_OF`
177
-
178
- **Relationship Direction:**
179
-
180
- Relationships support directional semantics with **automatic semantic defaults** that determine **hierarchical score propagation**:
181
-
182
- ```python
183
- from cyvest import RelationshipType, RelationshipDirection
184
-
185
- # Automatically gets OUTBOUND (domain → IP)
186
- # IP is a child of domain, IP's score propagates UP to domain
187
- # Accepts observable proxies directly
188
- cv.observable_add_relationship(domain, ip, RelationshipType.RESOLVES_TO)
189
-
190
- # Automatically gets INBOUND (file ← URL)
191
- # URL is parent of file, file's score propagates UP to URL
192
- cv.observable_add_relationship(malware, url, RelationshipType.DOWNLOADED)
193
-
194
- # Automatically gets BIDIRECTIONAL (host ↔ host)
195
- # No hierarchical propagation - scores remain independent
196
- cv.observable_add_relationship(host1, host2, RelationshipType.COMMUNICATES_WITH)
197
-
198
- # Can override semantic defaults if needed
199
- cv.observable_add_relationship(
200
- domain, ip, # Accepts observable proxies
201
- RelationshipType.RESOLVES_TO,
202
- RelationshipDirection.INBOUND # explicit override
203
- )
204
- ```
205
-
206
- **Direction-Based Hierarchical Scoring:**
207
-
208
- Relationship directions define parent-child hierarchies for score propagation:
209
-
210
- - **OUTBOUND (→)**: `source → target` — Target is a **child** of source
211
- - Source's score includes child's score: `score = max(TI_scores, child_scores)`
212
- - Example: `domain → IP` means IP score flows up to domain
213
-
214
- - **INBOUND (←)**: `source ← target` — Target is a **parent** of source
215
- - Target's score includes source's score
216
- - Example: `file ← URL` means file score flows up to URL
217
-
218
- - **BIDIRECTIONAL (↔)**: `source ↔ target` — **No hierarchy**
219
- - Scores do NOT propagate between observables
220
- - Each maintains independent score from its own threat intel
221
- - Example: `host1 ↔ host2` keeps separate scores
222
-
223
- **Semantic Default Directions:**
224
-
225
- Each relationship type has an intuitive default direction:
226
- - **OUTBOUND (→)**: `RESOLVES_TO`, `BELONGS_TO`, `CONTAINS`, `TO`, `CC`, `BCC`, `CREATED`, `OPENED`, `PARENT`, `VALUES`
227
- - **INBOUND (←)**: `DOWNLOADED`, `DROPPED`, `FROM`, `SENDER`, `CHILD`, `DERIVED_FROM`
228
- - **BIDIRECTIONAL (↔)**: `COMMUNICATES_WITH`, `RELATED_TO`, `DUPLICATE_OF`
229
-
230
- Direction symbols appear in visualizations, markdown exports, and determine score flow.
150
+ Cyvest ships enums for the most common observable types; you can still pass strings for custom types.
151
+ Relationships are intentionally simple for now: use `RelationshipType.RELATED_TO` to link observables
152
+ and optionally choose a direction (`OUTBOUND`, `INBOUND`, or `BIDIRECTIONAL`) to control score propagation.
231
153
 
232
154
  ### Checks
233
155
 
@@ -430,6 +352,9 @@ cyvest merge inv1.json inv2.json -o merged.json -f rich --stats
430
352
 
431
353
  # Generate an interactive visualization (requires visualization extra)
432
354
  cyvest visualize investigation.json --min-level SUSPICIOUS --group-by-type
355
+
356
+ # Output the JSON Schema describing serialized investigations
357
+ cyvest schema > schema.json
433
358
  ```
434
359
 
435
360
  ## Development
@@ -484,29 +409,15 @@ mkdocs serve
484
409
  mkdocs build
485
410
  ```
486
411
 
487
- ## Project Structure
412
+ ## JavaScript packages
488
413
 
489
- ```
490
- cyvest/
491
- ├── src/cyvest/
492
- │ ├── __init__.py # Package initialization
493
- │ ├── cyvest.py # High-level API facade
494
- │ ├── investigation.py # Core state management with merge-on-create
495
- │ ├── proxies.py # Read-only proxies + fluent helper methods
496
- │ ├── levels.py # Level enum and scoring logic
497
- │ ├── keys.py # Key generation utilities
498
- │ ├── model.py # Core data models
499
- │ ├── score.py # Scoring and propagation engine
500
- │ ├── stats.py # Statistics and aggregations
501
- │ ├── io_serialization.py # JSON and Markdown export
502
- │ ├── io_rich.py # Rich console output
503
- │ └── cli.py # CLI interface
504
- ├── examples/ # Example scripts
505
- ├── tests/ # Test suite
506
- ├── docs/ # Documentation
507
- ├── pyproject.toml # Project configuration
508
- └── README.md # This file
509
- ```
414
+ The repo includes a PNPM workspace under `js/` with three packages:
415
+
416
+ - `@cyvest/cyvest-js`: TypeScript types, schema validation, and helpers for Cyvest investigations.
417
+ - `@cyvest/cyvest-vis`: React components for graph visualization (depends on `@cyvest/cyvest-js`).
418
+ - `@cyvest/cyvest-app`: Vite demo that bundles the JS packages with sample investigations.
419
+
420
+ See `docs/js-packages.md` for workspace commands and usage snippets.
510
421
 
511
422
  ## Contributing
512
423
 
@@ -539,7 +450,6 @@ Cyvest is designed for:
539
450
  - **Deterministic Keys**: Same objects always generate same keys for merging
540
451
  - **Score Propagation**: Automatic hierarchical score calculation
541
452
  - **Flexible Export**: JSON for storage, Markdown for LLM analysis
542
- - **STIX2 Relationships**: Industry-standard relationship modeling
543
453
  - **Audit Trail**: Score change history for debugging
544
454
 
545
455
  ## Future Enhancements
@@ -10,8 +10,8 @@
10
10
  - 🔍 **Structured Investigation Modeling**: Model investigations with observables, checks, threat intelligence, and enrichments
11
11
  - 📊 **Automatic Scoring**: Dynamic score calculation and propagation through investigation hierarchy
12
12
  - 🎯 **Level Classification**: Automatic security level assignment (TRUSTED, INFO, SAFE, NOTABLE, SUSPICIOUS, MALICIOUS)
13
- - 🔗 **Relationship Tracking**: STIX2-compliant relationship modeling between observables
14
- - 🏷️ **STIX2 Type Support**: Built-in enums for STIX2 Observable and Relationship types with autocomplete
13
+ - 🔗 **Relationship Tracking**: Lightweight relationship modeling between observables
14
+ - 🏷️ **Typed Helpers**: Built-in enums for observable types and relationships with autocomplete
15
15
  - 📈 **Real-time Statistics**: Live metrics and aggregations throughout the investigation
16
16
  - 🔄 **Investigation Merging**: Combine investigations from multiple threads or processes
17
17
  - 🧵 **Multi-Threading Support**: Advanced thread-safe shared context available via `cyvest.investigation` module
@@ -55,7 +55,7 @@ from cyvest import Cyvest, Level, ObservableType, RelationshipType
55
55
 
56
56
  # Create an investigation
57
57
  with Cyvest(data={"type": "email"}) as cv:
58
- # Create observables with STIX2 types
58
+ # Create observables
59
59
  url = (
60
60
  cv.observable(ObservableType.URL, "https://phishing-site.com", internal=False)
61
61
  .with_ti("virustotal", score=Decimal("8.5"), level=Level.MALICIOUS)
@@ -99,108 +99,30 @@ Dictionary fields merge by default; pass `merge_extra=False` (or `merge_data=Fal
99
99
 
100
100
  ### Observables
101
101
 
102
- Observables represent cyber artifacts (URLs, IPs, domains, hashes, files, etc.). Use STIX2-compliant types for standardization:
102
+ Observables represent cyber artifacts (URLs, IPs, domains, hashes, files, etc.).
103
103
 
104
104
  ```python
105
- from cyvest import ObservableType, RelationshipType
105
+ from cyvest import ObservableType, RelationshipType, RelationshipDirection
106
106
 
107
- # Create observable with STIX2 type enum
108
107
  url_obs = cv.observable_create(
109
108
  ObservableType.URL,
110
109
  "https://malicious.com",
111
110
  internal=False
112
111
  )
113
112
 
114
- # Or use strings (backward compatible)
115
113
  ip_obs = cv.observable_create("ipv4-addr", "192.0.2.1", internal=False)
116
114
 
117
- # Add threat intelligence
118
- cv.observable_add_threat_intel(
119
- url_obs.key,
120
- source="virustotal",
121
- score=Decimal("9.0"),
122
- level=Level.MALICIOUS,
123
- comment="Detected as malware distribution site"
124
- )
125
-
126
- # Create relationships with STIX2 relationship types
127
- # Accepts observable proxies or string keys
128
115
  cv.observable_add_relationship(
129
116
  url_obs, # Can pass ObservableProxy directly
130
117
  ip_obs, # Or use .key for string keys
131
- RelationshipType.RESOLVES_TO
118
+ RelationshipType.RELATED_TO,
119
+ RelationshipDirection.BIDIRECTIONAL,
132
120
  )
133
121
  ```
134
122
 
135
- **Available STIX2 Observable Types:**
136
-
137
- - Network: `IPV4_ADDR`, `IPV6_ADDR`, `DOMAIN_NAME`, `URL`, `MAC_ADDR`, `NETWORK_TRAFFIC`
138
- - Email: `EMAIL_ADDR`, `EMAIL_MESSAGE`, `EMAIL_MIME_PART`
139
- - File: `FILE`, `DIRECTORY`, `ARTIFACT`
140
- - System: `PROCESS`, `SOFTWARE`, `USER_ACCOUNT`, `WINDOWS_REGISTRY_KEY`
141
- - Other: `AUTONOMOUS_SYSTEM`, `MUTEX`, `X509_CERTIFICATE`
142
-
143
- **Available STIX2 Relationship Types:**
144
-
145
- - Network: `RESOLVES_TO`, `BELONGS_TO`, `COMMUNICATES_WITH`
146
- - File: `CONTAINS`, `DOWNLOADED`, `DROPPED`
147
- - Email: `FROM`, `SENDER`, `TO`, `CC`, `BCC`
148
- - Process: `CREATED`, `OPENED`, `PARENT`, `CHILD`
149
- - General: `RELATED_TO`, `DERIVED_FROM`, `DUPLICATE_OF`
150
-
151
- **Relationship Direction:**
152
-
153
- Relationships support directional semantics with **automatic semantic defaults** that determine **hierarchical score propagation**:
154
-
155
- ```python
156
- from cyvest import RelationshipType, RelationshipDirection
157
-
158
- # Automatically gets OUTBOUND (domain → IP)
159
- # IP is a child of domain, IP's score propagates UP to domain
160
- # Accepts observable proxies directly
161
- cv.observable_add_relationship(domain, ip, RelationshipType.RESOLVES_TO)
162
-
163
- # Automatically gets INBOUND (file ← URL)
164
- # URL is parent of file, file's score propagates UP to URL
165
- cv.observable_add_relationship(malware, url, RelationshipType.DOWNLOADED)
166
-
167
- # Automatically gets BIDIRECTIONAL (host ↔ host)
168
- # No hierarchical propagation - scores remain independent
169
- cv.observable_add_relationship(host1, host2, RelationshipType.COMMUNICATES_WITH)
170
-
171
- # Can override semantic defaults if needed
172
- cv.observable_add_relationship(
173
- domain, ip, # Accepts observable proxies
174
- RelationshipType.RESOLVES_TO,
175
- RelationshipDirection.INBOUND # explicit override
176
- )
177
- ```
178
-
179
- **Direction-Based Hierarchical Scoring:**
180
-
181
- Relationship directions define parent-child hierarchies for score propagation:
182
-
183
- - **OUTBOUND (→)**: `source → target` — Target is a **child** of source
184
- - Source's score includes child's score: `score = max(TI_scores, child_scores)`
185
- - Example: `domain → IP` means IP score flows up to domain
186
-
187
- - **INBOUND (←)**: `source ← target` — Target is a **parent** of source
188
- - Target's score includes source's score
189
- - Example: `file ← URL` means file score flows up to URL
190
-
191
- - **BIDIRECTIONAL (↔)**: `source ↔ target` — **No hierarchy**
192
- - Scores do NOT propagate between observables
193
- - Each maintains independent score from its own threat intel
194
- - Example: `host1 ↔ host2` keeps separate scores
195
-
196
- **Semantic Default Directions:**
197
-
198
- Each relationship type has an intuitive default direction:
199
- - **OUTBOUND (→)**: `RESOLVES_TO`, `BELONGS_TO`, `CONTAINS`, `TO`, `CC`, `BCC`, `CREATED`, `OPENED`, `PARENT`, `VALUES`
200
- - **INBOUND (←)**: `DOWNLOADED`, `DROPPED`, `FROM`, `SENDER`, `CHILD`, `DERIVED_FROM`
201
- - **BIDIRECTIONAL (↔)**: `COMMUNICATES_WITH`, `RELATED_TO`, `DUPLICATE_OF`
202
-
203
- Direction symbols appear in visualizations, markdown exports, and determine score flow.
123
+ Cyvest ships enums for the most common observable types; you can still pass strings for custom types.
124
+ Relationships are intentionally simple for now: use `RelationshipType.RELATED_TO` to link observables
125
+ and optionally choose a direction (`OUTBOUND`, `INBOUND`, or `BIDIRECTIONAL`) to control score propagation.
204
126
 
205
127
  ### Checks
206
128
 
@@ -403,6 +325,9 @@ cyvest merge inv1.json inv2.json -o merged.json -f rich --stats
403
325
 
404
326
  # Generate an interactive visualization (requires visualization extra)
405
327
  cyvest visualize investigation.json --min-level SUSPICIOUS --group-by-type
328
+
329
+ # Output the JSON Schema describing serialized investigations
330
+ cyvest schema > schema.json
406
331
  ```
407
332
 
408
333
  ## Development
@@ -457,29 +382,15 @@ mkdocs serve
457
382
  mkdocs build
458
383
  ```
459
384
 
460
- ## Project Structure
385
+ ## JavaScript packages
461
386
 
462
- ```
463
- cyvest/
464
- ├── src/cyvest/
465
- │ ├── __init__.py # Package initialization
466
- │ ├── cyvest.py # High-level API facade
467
- │ ├── investigation.py # Core state management with merge-on-create
468
- │ ├── proxies.py # Read-only proxies + fluent helper methods
469
- │ ├── levels.py # Level enum and scoring logic
470
- │ ├── keys.py # Key generation utilities
471
- │ ├── model.py # Core data models
472
- │ ├── score.py # Scoring and propagation engine
473
- │ ├── stats.py # Statistics and aggregations
474
- │ ├── io_serialization.py # JSON and Markdown export
475
- │ ├── io_rich.py # Rich console output
476
- │ └── cli.py # CLI interface
477
- ├── examples/ # Example scripts
478
- ├── tests/ # Test suite
479
- ├── docs/ # Documentation
480
- ├── pyproject.toml # Project configuration
481
- └── README.md # This file
482
- ```
387
+ The repo includes a PNPM workspace under `js/` with three packages:
388
+
389
+ - `@cyvest/cyvest-js`: TypeScript types, schema validation, and helpers for Cyvest investigations.
390
+ - `@cyvest/cyvest-vis`: React components for graph visualization (depends on `@cyvest/cyvest-js`).
391
+ - `@cyvest/cyvest-app`: Vite demo that bundles the JS packages with sample investigations.
392
+
393
+ See `docs/js-packages.md` for workspace commands and usage snippets.
483
394
 
484
395
  ## Contributing
485
396
 
@@ -512,7 +423,6 @@ Cyvest is designed for:
512
423
  - **Deterministic Keys**: Same objects always generate same keys for merging
513
424
  - **Score Propagation**: Automatic hierarchical score calculation
514
425
  - **Flexible Export**: JSON for storage, Markdown for LLM analysis
515
- - **STIX2 Relationships**: Industry-standard relationship modeling
516
426
  - **Audit Trail**: Score change history for debugging
517
427
 
518
428
  ## Future Enhancements
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cyvest"
3
- version = "1.0.1"
3
+ version = "2.0.0"
4
4
  description = "Cybersecurity investigation model"
5
5
  readme = {file = "README.md", content-type = "text/markdown"}
6
6
  requires-python = ">=3.10"
@@ -40,6 +40,7 @@ build-backend = "uv_build"
40
40
  [dependency-groups]
41
41
  dev = [
42
42
  "debugpy>=1.8.17",
43
+ "jsonschema>=4.23.0",
43
44
  "pre-commit>=4.4.0",
44
45
  "pytest>=8.4.2",
45
46
  "pytest-cov>=6.0.0",
@@ -13,7 +13,7 @@ from cyvest.levels import Level
13
13
  from cyvest.model import CheckScorePolicy, ObservableType, RelationshipDirection, RelationshipType
14
14
  from cyvest.proxies import CheckProxy, ContainerProxy, EnrichmentProxy, ObservableProxy, ThreatIntelProxy
15
15
 
16
- __version__ = "1.0.1"
16
+ __version__ = "2.0.0"
17
17
 
18
18
  logger.disable("cyvest")
19
19
 
@@ -12,11 +12,12 @@ from pathlib import Path
12
12
  from typing import Any
13
13
 
14
14
  import click
15
- from logurich import init_logger, logger
15
+ from logurich import logger
16
16
  from logurich.opt_click import click_logger_params
17
17
  from rich.console import Console
18
18
 
19
19
  from cyvest import __version__
20
+ from cyvest.io_schema import get_investigation_schema
20
21
  from cyvest.io_serialization import load_investigation_json
21
22
  from cyvest.io_visualization import VisualizationDependencyMissingError
22
23
 
@@ -69,7 +70,6 @@ def _write_markdown(data: dict[str, Any], output_path: Path) -> None:
69
70
  @click.version_option(__version__, prog_name="Cyvest")
70
71
  def cli() -> None:
71
72
  """Cyvest - Cybersecurity Investigation Framework."""
72
- init_logger("INFO")
73
73
  logger.enable("cyvest")
74
74
  logger.info("> [green bold]CYVEST[/green bold]")
75
75
 
@@ -221,6 +221,28 @@ def export(input: Path, output: Path, export_format: str) -> None:
221
221
  logger.info(f"[green]Exported to Markdown: {output_path}[/green]")
222
222
 
223
223
 
224
+ @cli.command(name="schema")
225
+ @click.option(
226
+ "-o",
227
+ "--output",
228
+ type=click.Path(dir_okay=False, path_type=Path),
229
+ help="Write the JSON Schema to a file instead of stdout.",
230
+ )
231
+ def schema_cmd(output: Path | None) -> None:
232
+ """
233
+ Emit the JSON Schema describing serialized investigations.
234
+ """
235
+ schema = get_investigation_schema()
236
+ if output:
237
+ output_path = output.resolve()
238
+ output_path.parent.mkdir(parents=True, exist_ok=True)
239
+ output_path.write_text(json.dumps(schema, indent=2), encoding="utf-8")
240
+ logger.info(f"[green]Schema written to: {output_path}[/green]")
241
+ return
242
+
243
+ logger.rich("INFO", json.dumps(schema, indent=2), prefix=False)
244
+
245
+
224
246
  @cli.command()
225
247
  @click.argument("input", type=click.Path(exists=True, dir_okay=False, path_type=Path))
226
248
  @click.option(
@@ -29,7 +29,7 @@ from cyvest.io_serialization import (
29
29
  from cyvest.levels import Level, normalize_level
30
30
  from cyvest.model import Check, CheckScorePolicy, Container, Enrichment, Observable, ThreatIntel
31
31
  from cyvest.proxies import CheckProxy, ContainerProxy, EnrichmentProxy, ObservableProxy, ThreatIntelProxy
32
- from cyvest.score import ScoreMode
32
+ from cyvest.score import ScoreMode, normalize_score_mode
33
33
 
34
34
 
35
35
  class Cyvest:
@@ -44,7 +44,7 @@ class Cyvest:
44
44
  self,
45
45
  data: Any = None,
46
46
  root_type: Literal["file", "artifact"] = "file",
47
- score_mode: ScoreMode = ScoreMode.MAX,
47
+ score_mode: ScoreMode | Literal["max", "sum"] = ScoreMode.MAX,
48
48
  ) -> None:
49
49
  """
50
50
  Initialize a new investigation.
@@ -54,7 +54,8 @@ class Cyvest:
54
54
  root_type: Type of root observable ("file" or "artifact")
55
55
  score_mode: Score calculation mode (MAX or SUM)
56
56
  """
57
- self._investigation = Investigation(data, root_type=root_type, score_mode=score_mode)
57
+ normalized_score_mode = normalize_score_mode(score_mode)
58
+ self._investigation = Investigation(data, root_type=root_type, score_mode=normalized_score_mode)
58
59
 
59
60
  def __enter__(self) -> Cyvest:
60
61
  """Context manager entry."""
@@ -263,7 +264,7 @@ class Cyvest:
263
264
  Args:
264
265
  source: Source observable or its key
265
266
  target: Target observable or its key
266
- relationship_type: Type of relationship (STIX2 convention)
267
+ relationship_type: Type of relationship
267
268
  direction: Direction of the relationship (None = use semantic default for relationship type)
268
269
 
269
270
  Returns:
@@ -353,7 +354,7 @@ class Cyvest:
353
354
  extra: dict[str, Any] | None = None,
354
355
  score: Decimal | float | None = None,
355
356
  level: Level | str | None = None,
356
- score_policy: CheckScorePolicy | str | None = None,
357
+ score_policy: CheckScorePolicy | Literal["auto", "manual"] | None = None,
357
358
  ) -> CheckProxy:
358
359
  """
359
360
  Create a new check.
@@ -566,7 +567,13 @@ class Cyvest:
566
567
  save_investigation_json(self, filepath)
567
568
  return str(Path(filepath).resolve())
568
569
 
569
- def io_save_markdown(self, filepath: str | Path) -> str:
570
+ def io_save_markdown(
571
+ self,
572
+ filepath: str | Path,
573
+ include_containers: bool = False,
574
+ include_enrichments: bool = False,
575
+ include_observables: bool = True,
576
+ ) -> str:
570
577
  """
571
578
  Save the investigation as a Markdown report.
572
579
 
@@ -574,6 +581,9 @@ class Cyvest:
574
581
 
575
582
  Args:
576
583
  filepath: Path to save the Markdown file (relative or absolute)
584
+ include_containers: Include containers section in the report (default: False)
585
+ include_enrichments: Include enrichments section in the report (default: False)
586
+ include_observables: Include observables section in the report (default: True)
577
587
 
578
588
  Returns:
579
589
  Absolute path to the saved file as a string
@@ -587,13 +597,23 @@ class Cyvest:
587
597
  >>> path = cv.io_save_markdown("report.md")
588
598
  >>> print(path) # /absolute/path/to/report.md
589
599
  """
590
- save_investigation_markdown(self, filepath)
600
+ save_investigation_markdown(self, filepath, include_containers, include_enrichments, include_observables)
591
601
  return str(Path(filepath).resolve())
592
602
 
593
- def io_to_markdown(self) -> str:
603
+ def io_to_markdown(
604
+ self,
605
+ include_containers: bool = False,
606
+ include_enrichments: bool = False,
607
+ include_observables: bool = True,
608
+ ) -> str:
594
609
  """
595
610
  Generate a Markdown report of the investigation.
596
611
 
612
+ Args:
613
+ include_containers: Include containers section in the report (default: False)
614
+ include_enrichments: Include enrichments section in the report (default: False)
615
+ include_observables: Include observables section in the report (default: True)
616
+
597
617
  Returns:
598
618
  Markdown formatted report as a string
599
619
 
@@ -604,7 +624,7 @@ class Cyvest:
604
624
  # Cybersecurity Investigation Report
605
625
  ...
606
626
  """
607
- return generate_markdown_report(self)
627
+ return generate_markdown_report(self, include_containers, include_enrichments, include_observables)
608
628
 
609
629
  def io_to_dict(self) -> dict[str, Any]:
610
630
  """
@@ -782,7 +802,7 @@ class Cyvest:
782
802
  extra: dict[str, Any] | None = None,
783
803
  score: Decimal | float | None = None,
784
804
  level: Level | str | None = None,
785
- score_policy: CheckScorePolicy | str | None = None,
805
+ score_policy: CheckScorePolicy | Literal["auto", "manual"] | None = None,
786
806
  ) -> CheckProxy:
787
807
  """
788
808
  Create a check with fluent helper methods.