Skip to content

Triage Funnel

The triage funnel is Mayo ASPM's policy-driven filtering system that separates actionable findings from noise before ticket generation. It uses OPA (Open Policy Agent) policies to make consistent, automated triage decisions at scale.


Why triage matters

Security scanners produce findings of varying quality. Without triage, teams face:

  • Alert fatigue — hundreds of low-value findings burying real issues
  • Inconsistent decisions — different team members triaging differently
  • Wasted developer time — tickets created for false positives

The triage funnel solves this by codifying your triage logic as policy.


How the funnel works

All Findings
┌──────────────┐
│  Triage       │  OPA policies evaluate each finding
│  Policies     │  and assign a triage decision
└──────┬───────┘
       ├──▶ ACCEPT    → Finding is actionable → eligible for ticketing
       ├──▶ REJECT    → Finding is noise → auto-suppressed
       ├──▶ DEFER     → Finding needs human review → stays in triage queue
       └──▶ NO MATCH  → No policy matched → goes to manual triage

Triage decisions

Decision Effect Finding status
Accept Finding is confirmed as actionable Triaged → Confirmed
Reject Finding is suppressed as noise Triaged → Suppressed
Defer Finding needs manual review Triaged (stays in queue)

Writing triage policies

Triage policies are written in Rego and evaluate the input.finding object. A simple example:

package mayo.triage

import rego.v1

# Accept all critical findings automatically
decision := "accept" if {
    input.finding.severity == "critical"
}

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

# Reject known false positive pattern
decision := "reject" if {
    input.finding.scanner == "semgrep"
    contains(input.finding.rule_id, "generic.secrets.gitleaks")
    input.finding.file_path == "test/fixtures/fake-secrets.yml"
}

Policy evaluation order

If multiple triage policies match a finding, the most specific policy wins. Policies scoped to a project override organization-level policies. See Policy scoping.


The triage queue

Findings that are deferred or unmatched appear in the Triage Queue:

  1. Navigate to Triage in the left sidebar.
  2. The queue shows findings sorted by severity (critical first).
  3. For each finding, you can:
    • Accept — mark as actionable
    • Reject — suppress as noise
    • Create policy — write a policy rule based on this finding's attributes

Learn from manual triage

Every time you manually triage a finding, consider whether the decision could be automated. Click Create policy to generate a Rego rule from the finding's attributes.


Funnel metrics

The triage funnel dashboard shows:

Metric Description
Total findings All findings that entered the funnel
Auto-accepted Findings accepted by policy (no human needed)
Auto-rejected Findings rejected by policy
Deferred Findings waiting for human review
Unmatched Findings that no policy covered
Automation rate Percentage of findings handled by policy

Target automation rate

Mature organizations achieve 80-95% automation rates. Start by writing policies for your most common triage decisions and iterate.


Funnel stages in detail

Stage 1 — Ingestion

Findings arrive from scanners (SCA, SAST, secret detection, container scanning). Each finding is normalized into Mayo ASPM's common format with fields like severity, scanner, rule_id, file_path, package, and cve_id.

Stage 2 — Policy evaluation

All active triage policies are evaluated against each finding. The evaluation produces one of: accept, reject, defer, or no decision.

Stage 3 — Decision application

The decision is applied to the finding:

  • Accepted findings move to the Confirmed state and become eligible for ticket generation.
  • Rejected findings move to Suppressed and are hidden from default views.
  • Deferred findings remain in the Triaged state and appear in the triage queue.

Stage 4 — Ticket eligibility

Only accepted findings can be included in ticket generation. This is the key value of the funnel — it ensures only real issues reach developers.


Common triage patterns

Suppress test files

decision := "reject" if {
    contains(input.finding.file_path, "/test/")
}

Accept known-exploited vulnerabilities

decision := "accept" if {
    input.finding.cve_id in data.kev_list
}

Defer medium-severity for review

decision := "defer" if {
    input.finding.severity == "medium"
}

Next steps