cyvest 1.0.1__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.
cyvest-1.0.1/PKG-INFO ADDED
@@ -0,0 +1,548 @@
1
+ Metadata-Version: 2.3
2
+ Name: cyvest
3
+ Version: 1.0.1
4
+ Summary: Cybersecurity investigation model
5
+ Keywords: cybersecurity,investigation,threat-intel,security-analysis
6
+ Author: PakitoSec
7
+ Author-email: PakitoSec <jeromep83@gmail.com>
8
+ License: MIT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Security
17
+ Requires-Dist: click>=8
18
+ Requires-Dist: logurich[click]>=0.1
19
+ Requires-Dist: rich>=13
20
+ Requires-Dist: pyvis>=0.3.2 ; extra == 'visualization'
21
+ Requires-Python: >=3.10
22
+ Project-URL: Homepage, https://github.com/PakitoSec/cyvest
23
+ Project-URL: Issues, https://github.com/PakitoSec/cyvest/issues
24
+ Project-URL: Repository, https://github.com/PakitoSec/cyvest
25
+ Provides-Extra: visualization
26
+ Description-Content-Type: text/markdown
27
+
28
+ # Cyvest - Cybersecurity Investigation Framework
29
+
30
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
31
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
32
+
33
+ **Cyvest** is a Python framework for building, analyzing, and structuring cybersecurity investigations programmatically. It provides automatic scoring, level calculation, relationship tracking, and rich reporting capabilities.
34
+
35
+ ## Features
36
+
37
+ - ๐Ÿ” **Structured Investigation Modeling**: Model investigations with observables, checks, threat intelligence, and enrichments
38
+ - ๐Ÿ“Š **Automatic Scoring**: Dynamic score calculation and propagation through investigation hierarchy
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
42
+ - ๐Ÿ“ˆ **Real-time Statistics**: Live metrics and aggregations throughout the investigation
43
+ - ๐Ÿ”„ **Investigation Merging**: Combine investigations from multiple threads or processes
44
+ - ๐Ÿงต **Multi-Threading Support**: Advanced thread-safe shared context available via `cyvest.investigation` module
45
+ - ๐Ÿ’พ **Multiple Export Formats**: JSON and Markdown output for reporting and LLM consumption
46
+ - ๐ŸŽจ **Rich Console Output**: Beautiful terminal displays with the Rich library
47
+ - ๐Ÿงฉ **Fluent helpers**: Convenient API with method chaining for rapid development
48
+
49
+ ## Installation
50
+
51
+ ### Using uv (recommended)
52
+
53
+ ```bash
54
+ # Install uv if not already installed
55
+ curl -LsSf https://astral.sh/uv/install.sh | sh
56
+
57
+ # Clone the repository
58
+ git clone https://github.com/PakitoSec/cyvest.git
59
+ cd cyvest
60
+
61
+ # Install dependencies
62
+ uv sync
63
+
64
+ # Install in development mode
65
+ uv pip install -e .
66
+ ```
67
+
68
+ ### Using pip
69
+
70
+ ```bash
71
+ pip install -e .
72
+ ```
73
+
74
+ > Install the optional visualization extra with\
75
+ > `pip install "cyvest[visualization]"` (or `uv pip install -e ".[visualization]"`).
76
+
77
+ ## Quick Start
78
+
79
+ ```python
80
+ from decimal import Decimal
81
+ from cyvest import Cyvest, Level, ObservableType, RelationshipType
82
+
83
+ # Create an investigation
84
+ with Cyvest(data={"type": "email"}) as cv:
85
+ # Create observables with STIX2 types
86
+ url = (
87
+ cv.observable(ObservableType.URL, "https://phishing-site.com", internal=False)
88
+ .with_ti("virustotal", score=Decimal("8.5"), level=Level.MALICIOUS)
89
+ .relate_to(cv.root(), RelationshipType.RELATED_TO)
90
+ )
91
+
92
+ # Create checks
93
+ check = cv.check("url_analysis", "email_body", "Analyze suspicious URL")
94
+ check.link_observable(url)
95
+ check.with_score(Decimal("8.5"), "Malicious URL detected")
96
+
97
+ # Display results
98
+ print(f"Global Score: {cv.get_global_score()}")
99
+ print(f"Global Level: {cv.get_global_level()}")
100
+
101
+ # Export
102
+ from cyvest.io_serialization import save_investigation_json
103
+ save_investigation_json(cv, "investigation.json")
104
+ ```
105
+
106
+ ### Model Proxies
107
+
108
+ Cyvest only exposes immutable model proxies. Helpers like `observable_create`, `check_create`, and the
109
+ fluent `cv.observable()`/`cv.check()` convenience methods return `ObservableProxy`, `CheckProxy`, `ContainerProxy`, etc.
110
+ These proxies reflect the live investigation state but raise `AttributeError` if you try to assign to their attributes.
111
+ Use the facade helpers (`cv.observable_set_level`, `cv.check_update_score`, `cv.observable_add_threat_intel`) or the
112
+ built-in fluent methods on the proxies themselves (`with_ti`, `relate_to`, `link_observable`, `with_score`, โ€ฆ) so the
113
+ score engine runs automatically.
114
+
115
+ Safe metadata fields like `comment`, `extra`, or `internal` can be updated through the proxies without breaking score
116
+ consistency:
117
+
118
+ ```python
119
+ url_obs.update_metadata(comment="triaged", internal=False, extra={"ticket": "INC-4242"})
120
+ check.update_metadata(description="New scope", extra={"playbook": "url-analysis"})
121
+ ```
122
+
123
+ Dictionary fields merge by default; pass `merge_extra=False` (or `merge_data=False` for enrichments) to overwrite them.
124
+
125
+ ## Core Concepts
126
+
127
+ ### Observables
128
+
129
+ Observables represent cyber artifacts (URLs, IPs, domains, hashes, files, etc.). Use STIX2-compliant types for standardization:
130
+
131
+ ```python
132
+ from cyvest import ObservableType, RelationshipType
133
+
134
+ # Create observable with STIX2 type enum
135
+ url_obs = cv.observable_create(
136
+ ObservableType.URL,
137
+ "https://malicious.com",
138
+ internal=False
139
+ )
140
+
141
+ # Or use strings (backward compatible)
142
+ ip_obs = cv.observable_create("ipv4-addr", "192.0.2.1", internal=False)
143
+
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
+ cv.observable_add_relationship(
156
+ url_obs, # Can pass ObservableProxy directly
157
+ ip_obs, # Or use .key for string keys
158
+ RelationshipType.RESOLVES_TO
159
+ )
160
+ ```
161
+
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.
231
+
232
+ ### Checks
233
+
234
+ Checks represent verification steps in your investigation:
235
+
236
+ ```python
237
+ check = cv.check_create(
238
+ check_id="malware_detection",
239
+ scope="endpoint",
240
+ description="Verify file hash against threat intel",
241
+ score=Decimal("8.0"),
242
+ level=Level.MALICIOUS
243
+ )
244
+
245
+ # Link observables to checks
246
+ cv.check_link_observable(check.key, file_hash_obs.key)
247
+ ```
248
+
249
+ ### Threat Intelligence
250
+
251
+ Threat intelligence provides verdicts from external sources:
252
+
253
+ ```python
254
+ cv.observable_add_threat_intel(
255
+ observable.key,
256
+ source="virustotal",
257
+ score=Decimal("7.5"),
258
+ level=Level.SUSPICIOUS,
259
+ comment="15/70 vendors flagged as malicious",
260
+ taxonomies=[{"malware-type": "trojan"}]
261
+ )
262
+ ```
263
+
264
+ ### Containers
265
+
266
+ Containers organize checks hierarchically:
267
+
268
+ ```python
269
+ with cv.container("network_analysis") as network:
270
+ with network.sub_container("c2_detection") as c2:
271
+ check = cv.check("beacon_detection", "network", "Detect C2 beacons")
272
+ c2.add_check(check.get())
273
+ ```
274
+
275
+ ### Multi-Threaded Investigations
276
+
277
+ **Advanced Feature**: Use `SharedInvestigationContext` (imported directly from `cyvest.investigation`) for thread-safe parallel task execution with automatic observable sharing:
278
+
279
+ ```python
280
+ from cyvest import Cyvest
281
+ from cyvest.investigation import SharedInvestigationContext, InvestigationTask, Investigation
282
+ from concurrent.futures import ThreadPoolExecutor
283
+
284
+ class EmailAnalysisTask(InvestigationTask):
285
+ def run(self, shared_context):
286
+ # SharedInvestigationContext.create_cyvest() creates a Cyvest instance
287
+ # that auto-merges results when the context exits
288
+ with shared_context.create_cyvest() as cy:
289
+ # Access data from root observable
290
+ data = cy.root().extra
291
+
292
+ # Build investigation fragment
293
+ domain = cy.observable(ObservableType.DOMAIN_NAME, data.get("domain"))
294
+
295
+ # Auto-reconciles on exit
296
+ return cy
297
+
298
+ # Create shared context
299
+ main_inv = Investigation(email_data, root_type="artifact")
300
+ shared = SharedInvestigationContext(main_inv)
301
+
302
+ # Run tasks in parallel - they can reference each other's observables
303
+ with ThreadPoolExecutor(max_workers=4) as executor:
304
+ futures = [executor.submit(task.run, shared) for task in tasks]
305
+ for future in as_completed(futures):
306
+ future.result() # Auto-reconciled
307
+
308
+ # Get merged investigation (same object passed to SharedInvestigationContext)
309
+ final_investigation = main_inv
310
+ ```
311
+
312
+ See `examples/04_email.py` for a complete multi-threaded investigation example.
313
+
314
+ ### Scoring & Levels
315
+
316
+ Scores and levels are automatically calculated and propagated:
317
+
318
+ - **Threat Intel โ†’ Observable**: Observable score = **max** of all threat intel scores (not sum)
319
+ - **Observable Hierarchy**: Parent observable scores include child observable scores based on relationship direction:
320
+ - **OUTBOUND relationships**: target scores propagate to source (source is parent)
321
+ - **INBOUND relationships**: source scores propagate to target (target is parent)
322
+ - **BIDIRECTIONAL relationships**: no hierarchical propagation
323
+ - **Observable โ†’ Check**: Check score = **max** of all linked observables' scores and check's current score
324
+ - **Manual checks**: Set `score_policy=CheckScorePolicy.MANUAL` (or `check.disable_auto_score()`) to prevent observable-driven score/level changes
325
+ - **Check โ†’ Global**: All check scores sum to global investigation score
326
+
327
+ Score to Level mapping:
328
+
329
+ - `< 0.0` โ†’ TRUSTED
330
+ - `== 0.0` โ†’ INFO
331
+ - `< 3.0` โ†’ NOTABLE
332
+ - `< 5.0` โ†’ SUSPICIOUS
333
+ - `>= 5.0` โ†’ MALICIOUS
334
+
335
+ **SAFE Level Protection:**
336
+
337
+ The SAFE level has special protection for trusted/whitelisted observables:
338
+
339
+ ```python
340
+ # Mark a known-good domain as SAFE
341
+ trusted = cv.observable_create(
342
+ "domain",
343
+ "trusted.example.com",
344
+ level=Level.SAFE
345
+ )
346
+
347
+ # Adding low-score threat intel won't downgrade to TRUSTED or INFO
348
+ cv.observable_add_threat_intel(trusted.key, "source1", score=Decimal("0"))
349
+ # Level stays SAFE, score updates to 0
350
+
351
+ # But high-score threat intel can still upgrade to MALICIOUS if warranted
352
+ cv.observable_add_threat_intel(trusted.key, "source2", score=Decimal("6.0"))
353
+ # Level upgrades to MALICIOUS, score updates to 6.0
354
+
355
+ # Threat intel with SAFE level can also mark observables as SAFE
356
+ uncertain = cv.observable_create("domain", "example.com")
357
+ cv.observable_add_threat_intel(
358
+ uncertain.key,
359
+ "whitelist_service",
360
+ score=Decimal("0"),
361
+ level=Level.SAFE
362
+ )
363
+ # Observable upgraded to SAFE level with automatic downgrade protection
364
+ ```
365
+
366
+ SAFE observables:
367
+ - Cannot be downgraded to lower levels (NONE, TRUSTED, INFO)
368
+ - Can be upgraded to higher levels (NOTABLE, SUSPICIOUS, MALICIOUS)
369
+ - Score values still update based on threat intelligence
370
+ - Protection is preserved during investigation merges
371
+ - Can be marked SAFE by threat intel sources (e.g., whitelists, reputation databases)
372
+
373
+ SAFE checks:
374
+ - Automatically inherit SAFE level when linked to SAFE observables (if all other observables are โ‰ค SAFE)
375
+ - Can still upgrade to higher levels when NOTABLE/SUSPICIOUS/MALICIOUS observables are linked
376
+
377
+ **Root Observable Barrier:**
378
+
379
+ The root observable (the investigation's entry point with `value="input-data"`) acts as a special barrier to prevent cross-contamination:
380
+
381
+ **Barrier as Child** - When root appears as a child of other observables, it is **skipped** in their score calculations.
382
+
383
+ **Barrier as Parent** - Root's propagation is asymmetric:
384
+ - Root **CAN** be updated when children change (aggregates child scores)
385
+ - Root **does NOT** propagate upward beyond itself (stops recursive propagation)
386
+ - Root **DOES** propagate to checks normally
387
+
388
+ This design enables flexible investigation structures while preventing unintended score contamination.
389
+
390
+ ## Examples
391
+
392
+ See the `examples/` directory for complete examples:
393
+
394
+ - **01_email_basic.py**: Basic email phishing investigation
395
+ - **02_urls_and_ips.py**: Network investigation with URLs and IPs
396
+ - **03_merge_demo.py**: Multi-process investigation merging
397
+ - **04_email.py**: Multi-threaded investigation with SharedInvestigationContext
398
+ - **05_visualization.py**: Interactive HTML visualization showcasing scores, levels, and relationship flows
399
+
400
+ Run an example:
401
+
402
+ ```bash
403
+ python examples/01_email_basic.py
404
+ python examples/04_email.py
405
+ python examples/05_visualization.py
406
+ ```
407
+
408
+ ## CLI Usage
409
+
410
+ Cyvest includes a command-line interface for working with investigation files:
411
+
412
+ ```bash
413
+ # Display investigation
414
+ cyvest show investigation.json --graph
415
+
416
+ # Show statistics
417
+ cyvest stats investigation.json --detailed
418
+
419
+ # Export to markdown
420
+ cyvest export investigation.json -o report.md -f markdown
421
+
422
+ # Merge investigations with automatic deduplication
423
+ cyvest merge inv1.json inv2.json inv3.json -o merged.json
424
+
425
+ # Merge with statistics display
426
+ cyvest merge inv1.json inv2.json -o merged.json --stats
427
+
428
+ # Merge and display rich summary
429
+ cyvest merge inv1.json inv2.json -o merged.json -f rich --stats
430
+
431
+ # Generate an interactive visualization (requires visualization extra)
432
+ cyvest visualize investigation.json --min-level SUSPICIOUS --group-by-type
433
+ ```
434
+
435
+ ## Development
436
+
437
+ ### Setup Development Environment
438
+
439
+ ```bash
440
+ # Install development dependencies
441
+ uv sync --all-extras
442
+
443
+ # Run tests
444
+ pytest
445
+
446
+ # Run tests with coverage
447
+ pytest --cov=cyvest --cov-report=html
448
+
449
+ # Format code
450
+ ruff format .
451
+
452
+ # Lint code
453
+ ruff check .
454
+ ```
455
+
456
+ ### Running Tests
457
+
458
+ ```bash
459
+ # Run all tests
460
+ pytest
461
+
462
+ # Run specific test file
463
+ pytest tests/test_score.py
464
+
465
+ # Run with verbose output
466
+ pytest -v
467
+
468
+ # Run with coverage
469
+ pytest --cov=cyvest
470
+ ```
471
+
472
+ ## Documentation
473
+
474
+ Build the documentation with MkDocs:
475
+
476
+ ```bash
477
+ # Install docs dependencies
478
+ uv sync --all-extras
479
+
480
+ # Serve documentation locally
481
+ mkdocs serve
482
+
483
+ # Build documentation
484
+ mkdocs build
485
+ ```
486
+
487
+ ## Project Structure
488
+
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
+ ```
510
+
511
+ ## Contributing
512
+
513
+ Contributions are welcome! Please:
514
+
515
+ 1. Fork the repository
516
+ 2. Create a feature branch
517
+ 3. Make your changes with tests
518
+ 4. Run the test suite
519
+ 5. Submit a pull request
520
+
521
+ ## License
522
+
523
+ This project is licensed under the MIT License - see the LICENSE file for details.
524
+
525
+ ## Use Cases
526
+
527
+ Cyvest is designed for:
528
+
529
+ - **Security Operations Centers (SOCs)**: Automate investigation workflows
530
+ - **Incident Response**: Structure and document incident investigations
531
+ - **Threat Hunting**: Build repeatable hunting methodologies
532
+ - **Malware Analysis**: Track relationships between artifacts
533
+ - **Phishing Analysis**: Analyze emails and linked resources
534
+ - **Integration**: Combine results from multiple security tools
535
+
536
+ ## Architecture Highlights
537
+
538
+ - **Thread-Safe**: Advanced `SharedInvestigationContext` (via `cyvest.investigation`) provides thread-safe parallel task execution
539
+ - **Deterministic Keys**: Same objects always generate same keys for merging
540
+ - **Score Propagation**: Automatic hierarchical score calculation
541
+ - **Flexible Export**: JSON for storage, Markdown for LLM analysis
542
+ - **STIX2 Relationships**: Industry-standard relationship modeling
543
+ - **Audit Trail**: Score change history for debugging
544
+
545
+ ## Future Enhancements
546
+
547
+ - Database persistence layer
548
+ - Additional export formats (PDF, HTML)