Skip to content

Commit 794ce00

Browse files
leliaclaude
andcommitted
fix(slack): normalize the API's "middle" severity to "medium"
Every severity lookup in the Slack reachability formatter is keyed on "medium", but "middle" is what the API sends. A mid-severity finding missed all of them at once: uncounted in the summary, excluded from total_findings so the "and N more" count can go negative, and sorted at the default order of 4 -- below "low" -- so it was truncated out of the message first. Normalized at the point the alert is read rather than by adding a parallel key to each dict, so one canonical spelling flows downstream. The GitLab severity map and the PR comment path already accept both forms; this formatter did not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 1d193c7 commit 794ce00

6 files changed

Lines changed: 100 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# Changelog
22

3+
## 2.6.13
4+
5+
### Fixed: mid-severity findings were dropped from the Slack summary
6+
7+
- The Slack reachability formatter keyed every severity lookup on `medium`,
8+
but the API sends `middle`. A mid-severity finding therefore missed all of
9+
them at once: it was not counted, so the summary always read `Medium: 0`; it
10+
was excluded from `total_findings`, which can drive the "and N more" count
11+
negative; and it sorted at the default order of 4, below `low`, so it was the
12+
first thing truncated when the Slack block limit was reached.
13+
- Severity is now normalized to one spelling when an alert is read, matching
14+
how the GitLab and PR-comment paths already handle both forms. The findings
15+
themselves were always listed; only the counts, ordering and truncation were
16+
wrong.
17+
318
## 2.6.12
419

520
### Fixed: unreadable reachability facts no longer report a blocking package

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
66

77
[project]
88
name = "socketsecurity"
9-
version = "2.6.12"
9+
version = "2.6.13"
1010
requires-python = ">= 3.11"
1111
license = {"file" = "LICENSE"}
1212
dependencies = [

socketsecurity/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
__author__ = 'socket.dev'
2-
__version__ = '2.6.12'
2+
__version__ = '2.6.13'
33
USER_AGENT = f'SocketPythonCLI/{__version__}'

socketsecurity/plugins/formatters/slack.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ def _extract_alert_info(component: Dict[str, Any], alert: Dict[str, Any]) -> Dic
108108
"""
109109
props = alert.get('props', {}) or {}
110110
severity = str(alert.get('severity') or props.get('severity') or '').lower()
111+
# The API's mid-level severity is "middle"; every lookup in this module is
112+
# keyed on "medium". Normalizing here rather than adding a parallel key to
113+
# each dict keeps one canonical spelling downstream, matching what
114+
# Messages.map_socket_severity_to_gitlab already does.
115+
if severity == 'middle':
116+
severity = 'medium'
111117

112118
return {
113119
'cve_id': str(props.get('ghsaId') or props.get('cveId') or alert.get('title') or 'Unknown'),
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""The Slack formatter keys on "medium"; the API sends "middle".
2+
3+
Every severity lookup in ``socketsecurity/plugins/formatters/slack.py`` is keyed
4+
on ``medium``, but ``middle`` is what the API actually emits -- it is the value
5+
in the OpenAPI spec's ``SocketIssueSeverity`` and in the SDK enum. Unnormalized,
6+
a mid-severity finding fell through every one of them at once:
7+
8+
* it was not counted, so the summary always read ``Medium: 0``
9+
* it was excluded from ``total_findings``, which can drive ``omitted_count``
10+
negative when mid-severity findings are the ones being displayed
11+
* it sorted at the default order of 4, below ``low``, so it was truncated out of
12+
the message first when the block limit was reached
13+
14+
Two other call sites already handle both spellings (``Messages.map_socket_
15+
severity_to_gitlab`` and the GitLab severity map); this formatter did not.
16+
"""
17+
18+
import unittest
19+
20+
from socketsecurity.plugins.formatters.slack import (
21+
SEVERITY_EMOJI,
22+
SEVERITY_ORDER,
23+
_extract_alert_info,
24+
format_socket_facts_for_slack,
25+
)
26+
27+
28+
def _component(severity: str) -> dict:
29+
return {
30+
"name": "example-package",
31+
"version": "1.0.0",
32+
"alerts": [{"title": "Example alert", "severity": severity, "props": {}}],
33+
}
34+
35+
36+
class TestSeverityNormalization(unittest.TestCase):
37+
def test_middle_normalizes_to_medium(self):
38+
info = _extract_alert_info(_component("middle"), {"severity": "middle"})
39+
self.assertEqual(info["severity"], "medium")
40+
41+
def test_middle_gets_the_medium_order_not_the_default(self):
42+
info = _extract_alert_info(_component("middle"), {"severity": "middle"})
43+
self.assertEqual(info["severity_order"], SEVERITY_ORDER["medium"])
44+
# Regression: the default of 4 sorted mid-severity below "low".
45+
self.assertLess(info["severity_order"], SEVERITY_ORDER["low"])
46+
47+
def test_middle_gets_the_medium_emoji_not_the_fallback(self):
48+
info = _extract_alert_info(_component("middle"), {"severity": "middle"})
49+
self.assertEqual(info["severity_emoji"], SEVERITY_EMOJI["medium"])
50+
self.assertNotEqual(info["severity_emoji"], SEVERITY_EMOJI["low"])
51+
52+
def test_medium_still_works(self):
53+
info = _extract_alert_info(_component("medium"), {"severity": "medium"})
54+
self.assertEqual(info["severity"], "medium")
55+
self.assertEqual(info["severity_order"], SEVERITY_ORDER["medium"])
56+
57+
def test_middle_findings_are_counted_in_the_summary(self):
58+
result = format_socket_facts_for_slack([_component("middle")])
59+
self.assertEqual(len(result), 1)
60+
self.assertIn("🟡 Medium: 1", result[0]["summary"])
61+
62+
def test_middle_findings_reach_total_findings(self):
63+
# Regression: excluded from the total, omitted_count could go negative.
64+
result = format_socket_facts_for_slack([_component("middle")])
65+
self.assertEqual(result[0]["total_findings"], 1)
66+
67+
def test_unrecognized_severity_still_falls_back(self):
68+
info = _extract_alert_info(
69+
_component("brand-new-level"), {"severity": "brand-new-level"}
70+
)
71+
self.assertEqual(info["severity_order"], 4)
72+
self.assertEqual(info["severity_emoji"], "⚪")
73+
74+
75+
if __name__ == "__main__":
76+
unittest.main()

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)