Skip to content
Draft
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
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

## 2.6.13

### Fixed: mid-severity findings were dropped from the Slack summary

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

## 2.6.12

### Fixed: unreadable reachability facts no longer report a blocking package
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ build-backend = "hatchling.build"

[project]
name = "socketsecurity"
version = "2.6.12"
version = "2.6.13"
requires-python = ">= 3.11"
license = {"file" = "LICENSE"}
dependencies = [
Expand Down
2 changes: 1 addition & 1 deletion socketsecurity/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__author__ = 'socket.dev'
__version__ = '2.6.12'
__version__ = '2.6.13'
USER_AGENT = f'SocketPythonCLI/{__version__}'
6 changes: 6 additions & 0 deletions socketsecurity/plugins/formatters/slack.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ def _extract_alert_info(component: Dict[str, Any], alert: Dict[str, Any]) -> Dic
"""
props = alert.get('props', {}) or {}
severity = str(alert.get('severity') or props.get('severity') or '').lower()
# The API's mid-level severity is "middle"; every lookup in this module is
# keyed on "medium". Normalizing here rather than adding a parallel key to
# each dict keeps one canonical spelling downstream, matching what
# Messages.map_socket_severity_to_gitlab already does.
if severity == 'middle':
severity = 'medium'

return {
'cve_id': str(props.get('ghsaId') or props.get('cveId') or alert.get('title') or 'Unknown'),
Expand Down
76 changes: 76 additions & 0 deletions tests/unit/test_slack_severity_normalization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""The Slack formatter keys on "medium"; the API sends "middle".

Every severity lookup in ``socketsecurity/plugins/formatters/slack.py`` is keyed
on ``medium``, but ``middle`` is what the API actually emits -- it is the value
in the OpenAPI spec's ``SocketIssueSeverity`` and in the SDK enum. Unnormalized,
a mid-severity finding fell through every one of them at once:

* it was not counted, so the summary always read ``Medium: 0``
* it was excluded from ``total_findings``, which can drive ``omitted_count``
negative when mid-severity findings are the ones being displayed
* it sorted at the default order of 4, below ``low``, so it was truncated out of
the message first when the block limit was reached

Two other call sites already handle both spellings (``Messages.map_socket_
severity_to_gitlab`` and the GitLab severity map); this formatter did not.
"""

import unittest

from socketsecurity.plugins.formatters.slack import (
SEVERITY_EMOJI,
SEVERITY_ORDER,
_extract_alert_info,
format_socket_facts_for_slack,
)


def _component(severity: str) -> dict:
return {
"name": "example-package",
"version": "1.0.0",
"alerts": [{"title": "Example alert", "severity": severity, "props": {}}],
}


class TestSeverityNormalization(unittest.TestCase):
def test_middle_normalizes_to_medium(self):
info = _extract_alert_info(_component("middle"), {"severity": "middle"})
self.assertEqual(info["severity"], "medium")

def test_middle_gets_the_medium_order_not_the_default(self):
info = _extract_alert_info(_component("middle"), {"severity": "middle"})
self.assertEqual(info["severity_order"], SEVERITY_ORDER["medium"])
# Regression: the default of 4 sorted mid-severity below "low".
self.assertLess(info["severity_order"], SEVERITY_ORDER["low"])

def test_middle_gets_the_medium_emoji_not_the_fallback(self):
info = _extract_alert_info(_component("middle"), {"severity": "middle"})
self.assertEqual(info["severity_emoji"], SEVERITY_EMOJI["medium"])
self.assertNotEqual(info["severity_emoji"], SEVERITY_EMOJI["low"])

def test_medium_still_works(self):
info = _extract_alert_info(_component("medium"), {"severity": "medium"})
self.assertEqual(info["severity"], "medium")
self.assertEqual(info["severity_order"], SEVERITY_ORDER["medium"])

def test_middle_findings_are_counted_in_the_summary(self):
result = format_socket_facts_for_slack([_component("middle")])
self.assertEqual(len(result), 1)
self.assertIn("🟡 Medium: 1", result[0]["summary"])

def test_middle_findings_reach_total_findings(self):
# Regression: excluded from the total, omitted_count could go negative.
result = format_socket_facts_for_slack([_component("middle")])
self.assertEqual(result[0]["total_findings"], 1)

def test_unrecognized_severity_still_falls_back(self):
info = _extract_alert_info(
_component("brand-new-level"), {"severity": "brand-new-level"}
)
self.assertEqual(info["severity_order"], 4)
self.assertEqual(info["severity_emoji"], "⚪")


if __name__ == "__main__":
unittest.main()
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.