fix(notifications): stop the severity filter from eating approval events - #412
Merged
Merged
Conversation
All three approval_* notifications are undeliverable on a default configuration, so a finding awaiting a decision blocks forever and nobody is told. Two independent gates run before a notification is delivered: ShouldNotifyEventType empty enabled_event_types -> the platform defaults ShouldNotify empty enabled_severities -> critical + high only DefaultEnabledEventTypes deliberately turns EventTypeApprovalRequested ON. The reasoning is recorded next to it: "An approval request is addressed to a human; if it reaches nobody the finding stays blocked indefinitely." The enqueue site then stamps a constant severity — "medium" for requested and rejected, "low" for approved (finding/vulnerability_service.go:2355, :2458, :2542). Neither is in the default critical+high set, so the severity gate drops every one of them. The event-type gate was opened on purpose and the severity gate closed it again, producing exactly the outcome that comment exists to prevent. The root cause is that EnqueueParams.Severity carries two different things. For new_finding, sla_breach and friends it IS the finding's severity, and an operator who leaves the filter at its default is saying "only critical and high findings" — honoring that is the whole point. For approval lifecycle events it is a constant chosen by the enqueue site that describes no finding at all, and no operator ever asked to suppress it. So the fix is not to bump the constants to "high", which would just launder a workflow event through a field that does not apply to it. SeverityFilterApplies decides per event type whether the severity gate is meaningful, and the approval trio is exempt. They remain fully controllable through the event-type filter, which is the switch that actually means "I do not want these". SendNotificationInput gains EventType so the single-integration path makes the same decision as the broadcast path. Empty is treated as filterable, so every existing caller keeps its current behavior. Adds a completeness gate: a new event type must be classified as severity-bearing or not, and the build fails if one is added without that decision. Verified to fire by removing a classification — it names the offending event type. Inheriting the default silently is how this bug happened.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
All three
approval_*notifications are undeliverable on a default configuration. A finding awaiting a decision blocks forever and nobody is told.Two gates, and the second silently defeats the first
DefaultEnabledEventTypes()deliberately turnsEventTypeApprovalRequestedon, with the reasoning recorded right next to it:The enqueue sites then stamp a constant severity:
approval_requestedmediumapproval_rejectedmediumapproval_approvedlowSomeone opened the event-type gate on purpose; the severity gate closed it again. The exact outcome that comment exists to prevent is what happens today.
I swept every enqueue site to check whether this was wider — it is not.
sla_breach(high) andfinding_priority_escalated(mapped from priority class) survive;new_finding/new_exposure/finding_assignedpass the real finding severity, which is correct. The approval trio is the whole of it.Why not just bump the constants to "high"
Because that launders a workflow event through a field that does not apply to it, and the next person reading
Severity: "high"on an approval request would reasonably conclude a high-severity finding was involved.The actual root cause is that
EnqueueParams.Severitycarries two different things:So
SeverityFilterApplies(EventType)decides per event type whether the severity gate is meaningful. The approval trio is exempt and remains fully controllable through the event-type filter — the switch that actually means "I do not want these".SendNotificationInputgainsEventTypeso the single-integration path makes the same decision as the broadcast path. Empty is treated as filterable, so every existing caller keeps its current behavior.Completeness gate
A new event type must now be classified as severity-bearing or not, or the build fails naming it:
Verified to fire, not just present — I removed one classification and confirmed it fails and names the offender, then restored it. Inheriting the default silently is exactly how this bug happened.
Verification
"medium"really is excluded by the default severity set, so it cannot pass vacuously.new_finding,new_exposure,finding_assigned,finding_priority_escalated,sla_breach— a fix that delivers everything is not a fix.GOWORK=off go build ./...— okGOWORK=off go test ./...againstapp_test— all greenGOWORK=off make lint-ci— cleanNot verified end-to-end: there are no
integration_notification_extensionsrows on the live database, so I could not observe a real Slack/Teams delivery. This is proven at the domain + dispatch layer, not against a live channel.