Cyvest¶
Build, score, and narrate cybersecurity investigations with a single fluent Python API.
Cyvest turns raw observables into curated stories—complete with scoring, provenance, and export-ready reports.
Start Fast¶
- Install using uv or pip.
- Model your first case via the Quick Start notebook.
- Master the vocabulary with Core Concepts.
Need help choosing the right entry point?
- Automating SOC workflows? Jump straight to Quick Start.
- Upgrading from Cyvest 5.x? Follow the v5 to v6 migration guide.
- Running multi-threaded tasks? See Shared Investigation Context.
- Contributing or extending the fluent helpers? Review the Contributing guide.
Platform Highlights¶
| Area | Why it matters | What to look at |
|---|---|---|
| Immutable facts | An append-only log you can audit, merge and re-score | cyvest.facts, Concepts |
| Derived scoring | No score is ever stored; every number comes with the terms that produced it | cyvest.evaluation, Scoring Model |
| Link basis | Each link states what it scores on, so a finding can hold at the intel it fetched | cyvest.enums.LinkBasis, Scoring Model |
| Shared context | Thread-safe fragments that reconcile into a single story | cyvest.shared.SharedInvestigationContext, Guide |
| Timeline | A chronology projected from the log, on two clocks | cyvest.evaluation.timeline, Guide |
| Comparison | Diff investigations with tolerance bands for regression testing | cyvest.compare, Guide |
| Observable extraction | Extract IOCs from text, markdown, or URLs with defang/refang support | cyvest.extract, Guide |
| Reporting | Export JSON, Markdown, or render rich terminal summaries | cyvest.io.serialization, cyvest.io.render, Quick Start |
Walkthrough in 60 Seconds¶
from cyvest import Cyvest
cv = Cyvest(root_data={"type": "email"})
phishing_url = (
cv.observable(cv.OBS.URL, "https://phishing.com", internal=False)
.with_ti("virustotal", 8.5)
)
cv.observable_add_relation(cv.root().key, phishing_url.key, cv.REL.EXTRACTION)
(
cv.finding("email_url_finding", "Analyze embedded URL")
.link_observable(phishing_url)
.with_weight(8.5)
)
print(cv.get_global_score(), cv.get_global_verdict())
cv.display_explanation(phishing_url.key) # and why
Best practice
Store investigation metadata (request ID, analyst, ticket link) in the root observable's extra field by passing root_data to Cyvest(...).
Facts in, report out
cv.observable_* / cv.finding_* append immutable facts and return thin proxies. Scores are
never stored on them: get_report() derives everything, so re-evaluating under another policy
changes the numbers without touching a single fact.
Architecture Snapshot¶
Cyvest (facade + fluent proxies)
└─ Investigation (thin orchestrator)
├─ FactStore ......... the append-only log: observables, relations, signals,
│ evidence, findings, decisions, tags
├─ Policy ............ weights, attenuation, decision bounds
├─ Engine ............ derives a Report from facts + policy
└─ IO / reporting .... JSON, Markdown, Rich, timeline
Design principles
- Facts are immutable and semantically keyed, so merges are lossless and idempotent.
- No derived value is ever stored; the report is the single place numbers live.
- A relation's kind decides whether score propagates — there is no direction flag.
- A link's basis decides what it reads: the whole observable, named signals, or nothing at all.
- The evaluator never reads the clock, so an archived report stays reproducible.
Typical Journeys¶
| Goal | Recommended Path |
|---|---|
| Evaluate Cyvest in <10 minutes | Getting Started |
| Upgrade an existing Cyvest 6.x integration | Migration from v6 to v7 |
| Upgrade an existing Cyvest 5.x integration | Migration from v5 to v6 |
| Understand observables vs. findings | Core Concepts |
| Understand where a number comes from | Scoring Model |
| Keep a finding at the intel it fetched | Scoring Model → Basis |
| Share state across threads | Shared Investigation Context |
| Compare investigations or regression test | Comparing Investigations |
| Extract IOCs from text or URLs | Observable Extraction |
| Extend scoring or fluent helpers | Contributing |
| Embed results in other tools | Quick Start → Exporting Results |
JavaScript Packages¶
@cyvest/cyvest-js: TypeScript types, schema validation, and graph helpers for Cyvest investigations.@cyvest/cyvest-vis: React component (Cytoscape +d3-force) for observable relationship graphs.@cyvest/cyvest-app: Vite demo bundling the JS packages with sample investigations.
See JavaScript packages guide for install and workspace commands.
Community¶
- github.com/PakitoSec/cyvest
- Report bugs or request features
- Join discussions and share patterns
Cyvest is MIT licensed. Contributions and issue reports are welcome!