Skip to content

Automate Triage with OPA

This guide walks you through building a policy set that automates 80%+ of your triage decisions, turning a manual bottleneck into a fast, consistent, policy-driven process.


Goal

By the end of this guide, you will have:

  • A set of triage policies covering common patterns
  • An automation rate above 80%
  • A clean triage queue with only findings that genuinely need human review

Time: ~25 minutes


Prerequisites

  • At least one completed scan with 50+ findings
  • Familiarity with the Policy Playground
  • Admin or policy-editor role

Step 1 — Assess your current state

  1. Navigate to the Triage view.
  2. Note the total finding count and breakdown:
    • How many are Critical? High? Medium? Low? Info?
    • How many are from each scanner?
    • How many are in test/vendor directories?

Write down the numbers — you'll compare after automation.


Step 2 — Create the severity router

This foundational policy handles the most common triage pattern — routing by severity:

  1. Navigate to Policies > New Policy.
  2. Kind: Triage, Name: triage-severity-routing.
  3. Paste:
package mayo.triage

import rego.v1

default decision := "defer"

# Auto-accept critical and high findings
decision := "accept" if {
    input.finding.severity in ["critical", "high"]
}

# Auto-reject informational findings
decision := "reject" if {
    input.finding.severity == "info"
}

reason := "Auto-accepted: critical or high severity" if {
    decision == "accept"
}

reason := "Auto-rejected: informational severity" if {
    decision == "reject"
}
  1. Test in the playground with sample findings of each severity.
  2. Save & Activate.

Step 3 — Create the path filter

Suppress findings in non-production code:

  1. Create a new policy: triage-non-production-paths.
  2. Paste:
package mayo.triage

import rego.v1

non_production_patterns := [
    "/test/",
    "/__tests__/",
    "/spec/",
    "/fixtures/",
    "/testdata/",
    "/vendor/",
    "/node_modules/",
    "/.github/",
    "/docs/",
    "/examples/"
]

decision := "reject" if {
    some pattern in non_production_patterns
    contains(input.finding.file_path, pattern)
}

reason := "Auto-rejected: finding is in a non-production path" if {
    decision == "reject"
}
  1. Test with findings from test directories.
  2. Save & Activate.

Step 4 — Create the noise filter

Suppress known-noisy scanner rules:

  1. Create a new policy: triage-noisy-rules.
  2. Start with an empty blocklist and populate it from your triage queue:
package mayo.triage

import rego.v1

# Add rules here as you identify noise from your scans
noisy_rules := {
    "generic.secrets.gitleaks.generic-api-key",
    "generic.secrets.gitleaks.generic-secret"
}

decision := "reject" if {
    input.finding.rule_id in noisy_rules
}

reason := concat("", [
    "Auto-rejected: noisy rule ",
    input.finding.rule_id
]) if {
    decision == "reject"
}
  1. Save & Activate.

Populating the blocklist

Review your triage queue over the next few days. Each time you manually reject a finding for the same rule, add that rule_id to the noisy_rules set and update the policy.


Step 5 — Create the KEV/EPSS booster

Auto-accept findings that are actively exploited:

  1. Create a new policy: triage-threat-intel.
  2. Paste:
package mayo.triage

import rego.v1

# Auto-accept CISA KEV findings regardless of severity
decision := "accept" if {
    input.finding.in_kev == true
}

# Auto-accept high-EPSS findings
decision := "accept" if {
    input.finding.epss_score > 0.5
    input.finding.severity in ["critical", "high", "medium"]
}

reason := "Auto-accepted: in CISA KEV catalog" if {
    input.finding.in_kev == true
}

reason := "Auto-accepted: high EPSS score" if {
    input.finding.epss_score > 0.5
}
  1. Save & Activate.

Step 6 — Re-evaluate all findings

Apply your new policies to existing findings:

  1. Navigate to Findings.
  2. Click Re-evaluate Triage.
  3. Wait for evaluation to complete (typically 10-30 seconds for a few hundred findings).

Step 7 — Check your automation rate

  1. Navigate to the Triage Funnel Dashboard.
  2. Compare before and after:
Metric Before After (expected)
Auto-accepted 0% 20-30%
Auto-rejected 0% 40-60%
Deferred/Unmatched 100% 15-30%
Automation rate 0% 70-85%

80% or higher?

If you hit 80%, congratulations. If not, review the deferred queue for additional patterns to automate.


Step 8 — Iterate on the deferred queue

The remaining deferred findings need attention:

  1. Open the Triage Queue (shows only deferred/unmatched findings).
  2. For each finding, make a decision:
    • Accept — it's a real issue
    • Reject — it's noise
    • Create policy — if you see a pattern
  3. Repeat weekly until the queue is manageable (< 20 findings per week).

Final policy set summary

Policy Purpose Expected impact
triage-severity-routing Route by severity 30-40% of findings
triage-non-production-paths Suppress test/vendor 15-25% of findings
triage-noisy-rules Suppress known noise 10-20% of findings
triage-threat-intel Boost KEV/EPSS findings 2-5% of findings

Verification

  • All four policies are active
  • Findings have been re-evaluated
  • Automation rate is > 80%
  • Triage queue contains only genuinely uncertain findings

Next steps