Core Concepts
Risks
The Risk Ops Board turns each company attack path into a tracked risk with a state machine, priority score, and lifecycle audit trail. Hunt, accept residual, escalate, or schedule a re-hunt — the loop closes back into coverage.
Overview
A risk in CraftedSignal is a company attack path with state. The threat model declares what the attacker could do; the Risk Ops Board tracks what you are doing about each one.
Every accepted attack path becomes a risk. Each risk carries a priority score, a coverage percentage, a state, and a full audit trail of every transition. The Risks page is where analysts triage, hunt, and close out exposure, and where threat-feed briefs connect to the risks they relate to.
Risks live at /threats/risks. The drawer opens on row click.
The state machine
Every risk moves through a deterministic lifecycle. Each transition is recorded in the lifecycle timeline with the actor, timestamp, and reason.
| State | Meaning |
|---|---|
suggested | Generated by the threat model, awaiting review |
accepted | An analyst has approved the risk for investigation |
hunting | A hunt anchored to this risk is running |
evidence_gathered | Hunt completed; an evidence summary is attached |
mitigated | A detection rule was promoted from the hunt; the risk is covered |
active_threat | Evidence indicates ongoing exploitation — escalate to IR |
residual_accepted | Risk acknowledged with no further action |
rejected | Terminal — the risk does not apply |
Legal transitions
suggested→acceptedorrejectedaccepted→hunting,residual_accepted, orrejectedhunting→evidence_gathered(or back toacceptedto pause)evidence_gathered→mitigated,active_threat, orresidual_acceptedmitigated→hunting(when a scheduled re-hunt fires)active_threat→huntingorresidual_acceptedresidual_accepted→hunting(analyst reopens)rejectedis terminal
State pills in the drawer use a fixed color scheme: blue (hunting), amber (evidence_gathered), emerald (mitigated), red (active_threat), zinc (residual_accepted), slate (default).
Priority
Each risk carries a 0–100 priority score. The board sorts by priority descending so the most urgent risks surface first.
priority = severity × likelihood × (1 − coverage) × staleness × scale
- Severity and likelihood are declared on the risk (low = 0.25, medium = 0.5, high = 0.75, critical = 1.0).
- Coverage is the fraction of attack-path steps detected by at least one rule. A risk with no rules pinned to any step has coverage 0; a risk with full per-step coverage has coverage 1.
- Staleness boosts risks that haven’t been hunted recently. Capped at 3× — a risk that has never been hunted always sits at the maximum boost.
- The scale factor normalizes the worst case (critical × critical × fully uncovered × never hunted) to 100.
Priority recomputes:
- On every load of the Risks page (rate-limited to once per minute per company).
- Whenever a detection’s MITRE techniques change (auto-rebinds coverage edges).
- Whenever a hunt is promoted to a rule (the new edge re-anchors the risk).
Coverage
Coverage links detection rules to risks via the detection_threats edge table. Every edge carries a source so you can tell automated coverage from analyst-driven coverage:
| Source | When it’s written |
|---|---|
promoted_from_hunt | A hunt query graduated into a detection. Confidence = 100. |
auto_technique_match | A detection’s MITRE techniques overlap with steps on the risk. Confidence = matched-steps ÷ total-steps × 100. |
manual | Reserved for analyst-driven linking. |
Auto-matched edges refresh whenever a detection’s technique list changes. Hunt-promoted and manual edges are preserved across recomputes — analyst intent always wins over the heuristic.
Threat-feed relevance
The threat feed never mints a risk. An incoming brief relates to the risks you already have: the platform intersects the brief’s MITRE techniques with the techniques of your accepted attack paths, and every overlapping path is a related risk, shown on the brief detail page and surfaced in the exposure and TI workbench views.
Techniques in a brief that appear in no accepted attack path are surfaced as coverage gaps. From the brief you model those uncovered techniques into an attack path in one step (it posts to /threats/paths/quick, prefilled with the techniques). A risk exists only once you accept that modeled path through the normal flow; nothing is created automatically and there is no separate candidate queue.
Because the relationship is derived at read time from the current model, it stays consistent automatically: accept a new attack path and the briefs that share its techniques immediately show it as a related risk.
The drawer
Click any row on the Risks page to open the drawer. It shows:
- State pill (color-coded as above) and priority badge (0–100).
- Severity / likelihood and hunts run count.
- Evidence summary — populated automatically when a hunt completes against this risk.
- Lifecycle timeline — every state transition with who fired it and why.
- Actions (only the ones legal from the current state are shown):
- Hunt this risk — creates a new hunt anchored to the risk and transitions to
hunting. - Accept residual — prompts for a note, then transitions to
residual_accepted. - Mark as active threat — for risks in
evidence_gathered, captures escalation context and transitions toactive_threat. - Schedule re-hunt now — for risks in
mitigated, immediately fires a fresh hunt.
- Hunt this risk — creates a new hunt anchored to the risk and transitions to
Hunting from a risk
When you click Hunt this risk, the platform creates a new hunt with:
- Title
"Hunt: <risk name>"and a hypothesis pointing back to the risk. CompanyAttackPathIDset to the risk ID — the hunt is anchored.- All active SIEMs selected by default (override on the new-hunt form if needed).
The hunt detail page renders a Covers kill chain card showing every step on the risk, with crosshairs on the steps the hunt is investigating and shields on steps already covered by production rules.
The hunt proposer also sources from accepted risks. Risk-anchored hypotheses are ranked by the underlying risk’s priority, so the suggested hunts at the top of /hunts are the ones the model says you should run next.
Re-hunt
When a risk is promoted to mitigated (a hunt graduated into a rule), the platform schedules a re-hunt 90 days out. The clock is stored as cap_next_review_at on the risk; a Temporal cron transitions the risk back to hunting and creates a fresh hunt at the scheduled time.
You can also re-hunt manually from the drawer’s Schedule re-hunt now action — useful when threat intel surfaces a new variant of the same TTP.
Precision-based rehunt
After a detection rule is promoted from a hunt, the platform tracks alert verdicts (true positive / false positive) through the promotion_outcomes table. When the rule accumulates 10 or more verdicts and its rolling precision drops below 40%, the platform automatically schedules a re-hunt for the linked risk. This returns the risk to hunting state so the hunt team can investigate whether the query needs tightening or the underlying detection has become too broad.
The low-precision threshold (40%) and the minimum sample size (10 verdicts) are fixed constants. Promoted rules are the only rules this applies to — hand-authored rules are not subject to the automatic precision gate.
The dashboard Detection Quality card shows the top 5 promoted rules with rolling precision below 60% (with ≥10 verdicts), giving analysts early visibility before the re-hunt threshold is reached.
Stale CAP notifications
A weekly Temporal workflow (every Monday at 09:00 UTC) scans for company attack paths that have stalled and sends a notification to prompt action:
| Condition | Threshold |
|---|---|
accepted state, no activity | > 14 days since accepted or created |
hunting state, no progress | > 30 days since last hunted or created |
Staleness is measured against cap_reviewed_at (for accepted) or cap_last_hunted_at (for hunting), falling back to cap_created_at when those fields are unset. The notification links directly to the Risk Ops Board.
This is a notification only — the state machine does not advance automatically. An analyst still needs to act on the risk.
Auto-accept
Risks generated with severity critical — or severity high with likelihood high or critical — are auto-accepted on creation. They land in the accepted state with reviewed_by = "auto-accept:severity" so they flow straight into the hunt queue. Less severe risks stay in suggested for analyst review.
This is intentional and not configurable per-tenant: the model already weighs severity heavily, so paths the model considers extreme need to be visible to hunters immediately.
Routes
| Route | Purpose |
|---|---|
GET /threats/risks | Risks list, filterable by state and coverage view |
GET /threats/risks/{id}/drawer | Drawer partial (HTMX) |
POST /threats/risks/{id}/hunt | Create hunt + transition to hunting |
POST /threats/risks/{id}/accept-residual | Transition to residual_accepted |
POST /threats/risks/{id}/mark-active-threat | Transition to active_threat |
POST /threats/risks/{id}/schedule-rehunt | Re-hunt a mitigated risk |
POST /threats/paths/quick | Model a brief’s uncovered techniques into an attack path |
Related
- Threat Model — how attack paths are declared and weighted.
- Hunts
— the action arm for risks in
accepted,hunting, ormitigated. - Threat Feed — where briefs are scored and related to risks.
- Threat Actors — the catalog briefs are enriched against.