Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions socket_basics/core/connector/opengrep/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,15 @@ def generate_notifications(self, components: List[Dict[str, Any]]) -> Dict[str,
groups: Dict[str, List[Dict[str, Any]]] = {}
for c in comps_map.values():
for a in c.get('alerts', []):
# Skip suppressed alerts. A rule disabled via *_disabled_rules
# (or otherwise suppressed) is normalized to action 'ignore'.
# Honor that here so ignored findings are excluded from every
# notifier (PR comment, Slack, Jira, ...), matching how the
# dashboard treats them. Suppressed alerts still ship in the
# uploaded facts; this only gates notifications.
if (a.get('action') or '').strip().lower() == 'ignore':
continue

# Filter by severity - only include alerts that match allowed severities
alert_severity = (a.get('severity') or '').strip().lower()
if alert_severity and hasattr(self, 'allowed_severities') and alert_severity not in self.allowed_severities:
Expand Down
97 changes: 97 additions & 0 deletions tests/test_notification_action_filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""Suppressed findings (action == 'ignore') must be excluded from notifications.

Regression test for a rule suppressed via *_disabled_rules (or any dashboard /
triage suppression): the alert is normalized to action 'ignore'. The dashboard
honors that, but notification generation previously keyed off severity alone, so
suppressed critical/high findings still posted to the PR comment, Slack, etc.

generate_notifications() now drops ignored alerts before building any notifier
output. Suppressed alerts still ship in the uploaded facts; only notifications
are gated.
"""

import sys
from pathlib import Path

import pytest

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from scripts.preview_pr_comments import make_mock_config

from socket_basics.core.connector.opengrep import OpenGrepScanner


def _make_scanner(config):
# generate_notifications() only needs .config and .allowed_severities;
# bypass __init__, which shells out to build the opengrep rule set.
scanner = OpenGrepScanner.__new__(OpenGrepScanner)
scanner.config = config
scanner.allowed_severities = {"critical", "high"}
return scanner


def _critical_alert(rule_id, action, snippet, line):
return {
"title": rule_id,
"severity": "critical",
"action": action,
"subType": "sast-generic",
"location": {"path": "slik/domain/GetActionablePlanStatusesUseCase.kt"},
"props": {
"ruleId": rule_id,
"filePath": "slik/domain/GetActionablePlanStatusesUseCase.kt",
"startLine": line,
"endLine": line,
"codeSnippet": snippet,
},
}


@pytest.fixture
def config():
return make_mock_config()


def test_ignored_alert_excluded_from_pr_notifications(config):
scanner = _make_scanner(config)
components = [
{
"id": "GetActionablePlanStatusesUseCase.kt",
"name": "GetActionablePlanStatusesUseCase.kt",
"type": "generic",
"alerts": [
_critical_alert("kotlin-sql-injection", "ignore", "SUPPRESSED_SNIPPET_XYZ", 66),
_critical_alert("kotlin-weak-hash", "error", "ACTIVE_SNIPPET_ABC", 70),
],
}
]

result = scanner.generate_notifications(components)
content = "\n".join(item["content"] for item in result.get("github_pr", []))

# The active finding survives.
assert "kotlin-weak-hash" in content
assert "ACTIVE_SNIPPET_ABC" in content

# The suppressed finding is gone, even though it is critical.
assert "kotlin-sql-injection" not in content
assert "SUPPRESSED_SNIPPET_XYZ" not in content

# Summary counts only the non-ignored critical.
assert "Critical: 1" in content
assert "Critical: 2" not in content


def test_all_ignored_yields_no_notifications(config):
scanner = _make_scanner(config)
components = [
{
"id": "f.kt",
"name": "f.kt",
"type": "generic",
"alerts": [_critical_alert("kotlin-sql-injection", "ignore", "X", 66)],
}
]

# Every alert suppressed -> no groups -> empty per-notifier mapping.
assert scanner.generate_notifications(components) == {}