# Detection prioritization model, v0.2.0

Purpose: put the backlog of detection ideas in the order they should be built, by how much each one is worth against how much it costs, and refuse to rank anything whose data is not in the SIEM yet. A team can score a candidate in five minutes, and every number comes with one line saying where it came from.

## The six inputs

Each candidate gets six whole-number scores. The ends of each scale are shown here; the full 1 to 5 wording for every input is in `framework.json` under `score_model.inputs`.

| Input | What it asks | Scores 1 | Scores 5 |
|---|---|---|---|
| threat_relevance (T), 1 to 5 | How recently, and how close to you, has this technique been used? | no public reporting in 24 months | seen in your own incidents or red team findings in the last 12 months |
| impact (I), 1 to 5 | What does the attacker reach if the technique succeeds? | reconnaissance on one host | direct action on tier-0 identity (domain admin and what controls it), an OT safety system, or a payment system |
| robustness (B), 1 to 5 | How hard is it to slip past this rule with the data you have? | matches a throwaway indicator such as a hash, IP, or domain | matches something every way of doing the technique must produce |
| telemetry_readiness (R), 0 to 5 | Is the data the rule needs already in the SIEM, in usable shape? | 0: the source is not collected at all | collected, parsed, enriched, and covered by the silence alert |
| build_effort (E), 1 to 5 | How many hours from writing the hypothesis to passing validation? | under 4 hours | over 40 hours, a new log source has to be brought in |
| run_cost (C), 1 to 5 | How many analyst minutes a day will the alerts consume? | under 5 minutes a day | over 3 hours a day |

Robustness uses the five levels from MITRE's Summiting the Pyramid: the higher the level, the more the attacker has to change to avoid the rule. Run cost is the alerts expected per day multiplied by the typical minutes to triage one.

## The formula

```
value = wT*T + wI*I + wB*B          what the rule is worth
cost  = wE*E + wC*C                 what it costs to build and to live with
score = (value / cost) * (R / 5)^k  scaled down when the data is not fully ready
```

A readiness of 0 sets the score to 0 and marks the rule blocked. Scores are rounded to two decimals. With every weight at 1 and k = 1, scores run from 0.06 to 7.50.

## Presets: four ways to lean

A preset changes one weight so the same backlog ranks differently. Every weight not named stays at 1.

| Preset | Change | Lean | Where it goes wrong |
|---|---|---|---|
| balanced | none | one score for the whole backlog | only as good as the six numbers |
| threat_led | wT = 2 | what attackers are doing | pulls effort toward techniques your logs cannot see yet |
| asset_led | wI = 2 | what matters most if it is hit | needs an asset inventory most teams lack |
| telemetry_led | k = 2 | what your logs can already show | searches under the streetlight and can leave the biggest threats uncovered |

A rule required by a regulation or an audit does not get a preset. It carries a `mandate` with the framework, the requirement number, and a due date. Mandated rules come with budget and deadlines, and often catch auditors better than attackers.

## Order of the queue

Mandated rules due within 90 days go first, ordered by due date. Everything else follows in score order, highest first. When two scores tie, the rule that costs less to run goes first.

## Evidence

Every one of the six numbers comes with one line saying where it came from: an incident id, a report section, a query, a ticket. CI rejects a record missing any of the six lines.

## A worked example

DET-0001, Kerberoasting (T1558.003), balanced preset.

```yaml
inputs:
  threat_relevance: 4     # named in the sector ISAC report, Q2 2026, section 3
  impact: 4               # a service account here is a step toward domain admin
  robustness: 4           # event 4769 with RC4 ticket encryption for a user account
  telemetry_readiness: 4  # domain controller logs are parsed, but not enriched
  build_effort: 2         # about 6 hours, comparable to DET-0007
  run_cost: 2             # about 10 analyst minutes a day, from the 7-day count
```

Value is 4 + 4 + 4 = 12. Cost is 2 + 2 = 4. Readiness scales it by 4/5. Score: 12 / 4 × 0.8 = 2.40.

For comparison, DET-0002, a new scheduled task created by a non-admin user (T1053.005), scores T 3, I 2, B 3, R 3, E 2, C 4: value 8, cost 6, readiness 3/5, score 1.33 × 0.6 = 0.80. The gap comes from run cost and readiness. Under threat_led the two score 3.20 and 1.10; under telemetry_led, 1.92 and 0.48. Kerberoasting stays ahead in all three.

## Where to begin

Three questions route a team to a path.

1. Do you have a log source inventory: every source feeding the SIEM, with its owner, how long it is kept, whether its fields are parsed, and whether an alert fires when it goes quiet?
2. Do you have the last 12 months of incidents, each tagged with the ATT&CK techniques the attacker used?
3. How many analyst hours a week go to building and tuning detections?

No inventory: build one first. Five columns are enough: source, owner, days kept, fields parsed yes or no, silence alert yes or no. Then ship DET-0000, the silence alert, for every source. Then score.

Inventory but no incident history: threat relevance comes from public prevalence lists and reports about your sector, which are the 3 and 4 on the scale. Score every candidate with readiness 3 or higher. Ship the top 10.

Inventory and history: techniques from your own incidents score 5 on threat relevance. Score the backlog. Ship the top 10.

Work on no more rules at once than the hours from question 3 divided by 8. Replace the 8 with your own measured hours per rule once you have them.

## When a rule has to go

A rule's floor is the typical minutes to triage one of its alerts divided by the most analyst minutes the program will spend per true positive, 120 by default. A rule that takes 30 minutes to triage must be right at least 25% of the time to stay live; one that takes 6 minutes, 5%.

Retire on any one of these: its true-positive share stayed below its floor for two 30-day periods in a row; it fired at least once in 180 days and was never right; the log source it needs has been missing for 30 days straight; the technique or the system it protects is gone. A rule that has not fired at all in 365 days gets a review first, because either the data stopped or the hypothesis was wrong.

## What CI does with score.yml

1. Checks the record against `score.schema.json`, including the six evidence lines.
2. Computes the score and status, and writes `score`, `status`, and `scored_on`. A score typed in by hand fails the build.
3. Orders the backlog as described above and writes `backlog.json`.
4. Writes `metrics.json` (rule count, share with tests, robustness mix, score spread) for the site.

The site's prioritize page runs the same formula in the browser: paste a backlog, move the five weights and k, export the ranking as CSV. The weights travel in the page's URL so a ranking can be shared.
