Priority Policy Reference¶
Priority policies assign a numeric priority score to findings after triage. This score determines the order in which findings appear in dashboards and which findings are ticketed first.
Package¶
Required output¶
| Variable | Type | Range | Description |
|---|---|---|---|
priority |
integer | 0-100 | Priority score (higher = more urgent) |
Optional outputs:
| Variable | Type | Description |
|---|---|---|
label |
string | Human-readable label (e.g., "P1", "Critical") |
reason |
string | Explanation of the score |
Input schema¶
The input includes all fields from the triage input, plus:
{
"finding": { "...same as triage input..." },
"triage_decision": "accept",
"organization": { "id": "string", "name": "string" }
}
Info
Priority policies only evaluate findings that were accepted during triage. Rejected and deferred findings are not scored.
Scoring strategy¶
A common approach is to start with a base score from severity, then adjust with modifiers:
Base score (severity)
+ EPSS modifier
+ Age modifier
+ Fix availability modifier
+ KEV modifier
─────────────────
= Final priority (capped at 100)
Examples¶
Simple severity-based scoring¶
package mayo.priority
import rego.v1
priority := 100 if input.finding.severity == "critical"
priority := 75 if input.finding.severity == "high"
priority := 50 if input.finding.severity == "medium"
priority := 25 if input.finding.severity == "low"
Weighted scoring with modifiers¶
package mayo.priority
import rego.v1
# Base score from severity
base := 80 if input.finding.severity == "critical"
base := 60 if input.finding.severity == "high"
base := 40 if input.finding.severity == "medium"
base := 20 if input.finding.severity == "low"
# EPSS modifier: +0 to +15 based on exploit probability
epss_mod := round(input.finding.epss_score * 15)
# KEV modifier: +10 if in CISA KEV
kev_mod := 10 if input.finding.in_kev == true
default kev_mod := 0
# Fix available: +5 if a fix exists
fix_mod := 5 if input.finding.fixed_version != ""
default fix_mod := 0
# Age penalty: +1 per 30 days, max +10
age_mod := min([round(input.finding.age_days / 30), 10])
# Final score capped at 100
raw := base + epss_mod + kev_mod + fix_mod + age_mod
priority := min([raw, 100])
# Human-readable label
label := "P1 — Critical" if priority >= 80
label := "P2 — High" if { priority >= 60; priority < 80 }
label := "P3 — Medium" if { priority >= 40; priority < 60 }
label := "P4 — Low" if priority < 40
Context-aware scoring¶
package mayo.priority
import rego.v1
# Production-facing assets get higher priority
base := 90 if {
input.finding.severity == "critical"
input.finding.asset.name in ["api-gateway", "auth-service", "payment-service"]
}
base := 70 if {
input.finding.severity == "critical"
}
default base := 50
priority := base
Priority labels¶
If you define a label output, it appears alongside the numeric score in the UI:
| Score range | Suggested label | Typical SLA |
|---|---|---|
| 80-100 | P1 — Critical | 24 hours |
| 60-79 | P2 — High | 7 days |
| 40-59 | P3 — Medium | 30 days |
| 0-39 | P4 — Low | 90 days |
Evaluation behavior¶
- Priority policies run after triage on accepted findings only.
- If multiple priority policies exist, the most specific scoped policy wins.
- Priority scores can be recalculated by triggering a re-evaluation from the findings view.
Testing tips¶
- Test with findings of each severity to verify your score distribution.
- Check that edge cases (EPSS = 0, no CVE, age = 0) produce reasonable scores.
- Verify that your labels align with your organization's SLA expectations.
Next steps¶
- Triage policies — runs before priority
- Ownership policies — assign findings to teams
- Writing Rego — syntax guide