Skip to content

Commit 9b1a282

Browse files
committed
Correct package and alert information links
Use package destinations and alert-specific documentation in JSON and security comments. Preserve existing alert fields and omit links when their source value is missing. Assisted-by: Codex:GPT-6
1 parent 313bcc1 commit 9b1a282

7 files changed

Lines changed: 296 additions & 8 deletions

File tree

‎docs/cli-reference.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,14 @@ from `report_url`, which is the display link and may include output options such
341341
as license-detail settings. These additions apply to standard Socket JSON, not
342342
FOSSA compatibility output.
343343
344+
Each selected alert in standard Socket JSON also includes `package_url` and
345+
`info_url`. `package_url` preserves the existing alert `url` value, which is a
346+
package overview link; `info_url` links to documentation for the alert's type.
347+
Either field is JSON null when its source value is unavailable. The existing
348+
`url` and `purl` fields retain their meanings and values. These per-alert links
349+
are distinct from the scan-reference URLs above and do not identify an
350+
individual alert or triage page.
351+
344352
Standard Socket JSON also includes four package-change arrays:
345353
`new_packages`, `updated_packages`, `removed_packages`, and `replaced_packages`.
346354
Rows contain `id`, `name`, `version`, `ecosystem`, `direct`, `author`, `size`,

‎socketsecurity/core/messages.py‎

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
from datetime import datetime, timezone
77
from html import escape
88
from pathlib import Path
9+
from typing import Optional
910

1011
from mdutils import MdUtils
1112
from prettytable import PrettyTable
1213

1314
from socketsecurity.core.classes import Diff, Issue, Purl
14-
from socketsecurity.core.report_urls import add_query_params
15+
from socketsecurity.core.report_urls import add_query_params, build_alert_links
1516

1617
log = logging.getLogger("socketcli")
1718

@@ -945,6 +946,25 @@ def get_view_report_url(diff: Diff) -> str:
945946
return diff.report_url
946947
return ""
947948

949+
@staticmethod
950+
def _format_alert_links(
951+
package_url: Optional[str], alert_type: Optional[str]
952+
) -> str:
953+
links = build_alert_links(package_url, alert_type)
954+
anchors: list[str] = []
955+
if links.package_url:
956+
anchors.append(
957+
f'<a href="{Messages.html_attr(links.package_url)}">This package</a>'
958+
)
959+
if links.info_url:
960+
anchors.append(
961+
f'<a href="{Messages.html_attr(links.info_url)}">'
962+
"About this alert type</a>"
963+
)
964+
if not anchors:
965+
return ""
966+
return f"<p>ℹ️ Read more on: {' | '.join(anchors)}</p>"
967+
948968
@staticmethod
949969
def security_comment_template(diff: Diff, config=None) -> str:
950970
"""
@@ -1011,6 +1031,9 @@ def security_comment_template(diff: Diff, config=None) -> str:
10111031
)
10121032
# Generate proper manifest URL
10131033
manifest_url = Messages.get_manifest_file_url(diff, alert.manifests, config)
1034+
alert_links = Messages._format_alert_links(
1035+
getattr(alert, "url", None), alert.type
1036+
)
10141037
pkg_label = Messages.html_text(f"{alert.pkg_name}@{alert.pkg_version}")
10151038
pkg_marker = Messages.comment_marker_text(f"{alert.pkg_name}@{alert.pkg_version}")
10161039
# Generate a table row for each alert
@@ -1033,10 +1056,7 @@ def security_comment_template(diff: Diff, config=None) -> str:
10331056
<p><strong>Note:</strong> {Messages.html_text(alert.description)}</p>
10341057
{patched_version_html}
10351058
<p><strong>Source:</strong> <a href="{Messages.html_attr(manifest_url)}">Manifest File</a></p>
1036-
<p>ℹ️ Read more on:
1037-
<a href="{Messages.html_attr(alert.purl)}">This package</a> |
1038-
<a href="{Messages.html_attr(alert.url)}">This alert</a> |
1039-
<a href="https://socket.dev/alerts/malware">What is known malware?</a></p>
1059+
{alert_links}
10401060
<blockquote>
10411061
<p><em>Suggestion:</em> {Messages.html_text(alert.suggestion)}</p>
10421062
{ignore_html}
@@ -1084,6 +1104,9 @@ def security_comment_template(diff: Diff, config=None) -> str:
10841104

10851105
# Generate proper manifest URL for license violations
10861106
license_manifest_url = Messages.get_manifest_file_url(diff, first_alert.manifests, config)
1107+
alert_links = Messages._format_alert_links(
1108+
getattr(first_alert, "url", None), first_alert.type
1109+
)
10871110

10881111
license_ignore_html = (
10891112
f"<p><em>Mark the package as acceptable risk:</em> To ignore this alert only in this pull request, reply with the comment "
@@ -1093,7 +1116,7 @@ def security_comment_template(diff: Diff, config=None) -> str:
10931116
) if show_ignore else ""
10941117
comment += f""" </ul>
10951118
<p><strong>From:</strong> <a href="{Messages.html_attr(license_manifest_url)}">Manifest File</a></p>
1096-
<p>ℹ️ Read more on: <a href="{Messages.html_attr(first_alert.purl)}">This package</a> | <a href="https://socket.dev/alerts/license">What is a license policy violation?</a></p>
1119+
{alert_links}
10971120
<blockquote>
10981121
<p><em>Next steps:</em> Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at <strong>support@socket.dev</strong>.</p>
10991122
<p><em>Suggestion:</em> Find a package that does not violate your license policy or adjust your policy to allow this package's license.</p>

‎socketsecurity/core/report_output.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
)
1010
from socketsecurity.core.classes import Diff, Purl
1111
from socketsecurity.core.messages import Messages
12+
from socketsecurity.core.report_urls import build_alert_links
1213

1314

1415
@dataclass(frozen=True)
@@ -141,6 +142,15 @@ def build_socket_json_report(
141142
)
142143
selected_diff = clone_diff_with_selected_alerts(diff_report, selected_alerts)
143144
report = Messages.create_security_comment_json(selected_diff)
145+
for alert_row in report["new_alerts"]:
146+
package_url = alert_row.get("url")
147+
alert_type = alert_row.get("type")
148+
links = build_alert_links(
149+
package_url if isinstance(package_url, str) else None,
150+
alert_type if isinstance(alert_type, str) else None,
151+
)
152+
alert_row["package_url"] = links.package_url
153+
alert_row["info_url"] = links.info_url
144154
legal_flag = getattr(config, "legal", False)
145155
repo = getattr(config, "repo", None)
146156
branch = getattr(config, "branch", None)

‎socketsecurity/core/report_urls.py‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
1+
from dataclasses import dataclass
12
from typing import Mapping, Optional
2-
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
3+
from urllib.parse import parse_qsl, quote, urlencode, urlsplit, urlunsplit
4+
5+
6+
@dataclass(frozen=True)
7+
class AlertLinks:
8+
package_url: Optional[str]
9+
info_url: Optional[str]
10+
11+
12+
def build_alert_links(
13+
package_url: Optional[str], alert_type: Optional[str]
14+
) -> AlertLinks:
15+
return AlertLinks(
16+
package_url=package_url or None,
17+
info_url=(
18+
f"https://socket.dev/alerts/{quote(alert_type, safe='')}"
19+
if alert_type
20+
else None
21+
),
22+
)
323

424

525
def add_query_params(

‎tests/core/test_json_package_changes_transport.py‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,12 +487,28 @@ def test_transitive_alert_and_package_json_keep_ancestor_namespaces(
487487
assert alert_diff.new_alerts[0].introduced_by == [
488488
tuple(row) for row in expected_sources
489489
]
490+
issue = alert_diff.new_alerts[0]
491+
expected_package_url = "https://socket.dev/pypi/package/leaf/overview/2.0.0"
492+
assert issue.url == expected_package_url
490493
_, source_text = Messages.create_sources(alert_diff.new_alerts[0], "raw")
491494
assert source_text == "npm/@one/widget@1.0.0;npm/@two/widget@1.0.0"
495+
comment = Messages.security_comment_template(alert_diff)
496+
assert f'<a href="{expected_package_url}">This package</a>' in comment
497+
assert (
498+
'<a href="https://socket.dev/alerts/envVars">About this alert type</a>'
499+
in comment
500+
)
501+
assert f'href="{issue.purl}"' not in comment
492502
alert_report = build_socket_json_report(
493503
alert_diff, CliConfig(api_token="fictional-local-only", repo="fixture-repo")
494504
)
495505
assert alert_report["new_alerts"][0]["introduced_by"] == expected_sources
506+
assert alert_report["new_alerts"][0]["url"] == issue.url
507+
assert alert_report["new_alerts"][0]["purl"] == issue.purl
508+
assert alert_report["new_alerts"][0]["package_url"] == expected_package_url
509+
assert alert_report["new_alerts"][0]["info_url"] == (
510+
"https://socket.dev/alerts/envVars"
511+
)
496512
assert alert_report["scan_failed"] is False
497513

498514
full_diff = core.create_diff_report(

‎tests/unit/test_report_output.py‎

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,3 +671,85 @@ def test_full_scan_package_inventory_does_not_become_change_lists():
671671
assert report["updated_packages"] == []
672672
assert report["removed_packages"] == []
673673
assert report["replaced_packages"] == []
674+
675+
676+
def test_alert_json_links_are_additive_and_do_not_mutate_real_models():
677+
issue = _issue("envVars", warn=True)
678+
issue.url = "https://packages.example.test/@scope/pkg?view=a&mode=b#source"
679+
issue.purl = "pkg:npm/@scope/pkg@1.2.3"
680+
issue.props = {"nested": {"value": ["kept"]}}
681+
diff = _diff(new_alerts=[issue])
682+
diff_before = copy.deepcopy(diff.__dict__)
683+
issue_before = copy.deepcopy(issue.__dict__)
684+
original_alert = json.loads(str(issue))
685+
686+
report = build_socket_json_report(diff, _config())
687+
serialized = json.loads(json.dumps(report))
688+
alert_row = serialized["new_alerts"][0]
689+
690+
assert alert_row == {
691+
**original_alert,
692+
"package_url": "https://packages.example.test/@scope/pkg?view=a&mode=b#source",
693+
"info_url": "https://socket.dev/alerts/envVars",
694+
}
695+
assert alert_row["url"] == original_alert["url"]
696+
assert alert_row["purl"] == "pkg:npm/@scope/pkg@1.2.3"
697+
assert set(report) == {
698+
"scan_failed",
699+
"new_alerts",
700+
"full_scan_id",
701+
"diff_url",
702+
"report_url",
703+
"diff_scan_id",
704+
"old_scan_id",
705+
"new_scan_id",
706+
"old_scan_url",
707+
"new_scan_url",
708+
"repo",
709+
"branch",
710+
"commit_sha",
711+
"legal_mode",
712+
"new_packages",
713+
"updated_packages",
714+
"removed_packages",
715+
"replaced_packages",
716+
}
717+
assert len(report) == 18
718+
assert diff.__dict__ == diff_before
719+
assert issue.__dict__ == issue_before
720+
assert "package_url" not in issue.__dict__
721+
assert "info_url" not in issue.__dict__
722+
723+
724+
def test_alert_json_links_follow_existing_selection_and_emit_nulls():
725+
new_alerts = [_issue("envVars", warn=True), _issue("highCVE", warn=True)]
726+
unchanged = _issue("unchangedType", warn=True)
727+
del new_alerts[1].url
728+
unchanged.type = ""
729+
unchanged.url = ""
730+
diff = _diff(new_alerts=new_alerts, unchanged_alerts=[unchanged])
731+
732+
ordinary = json.loads(json.dumps(build_socket_json_report(diff, _config())))
733+
strict = json.loads(
734+
json.dumps(build_socket_json_report(diff, _config(strict_blocking=True)))
735+
)
736+
737+
assert [row["type"] for row in ordinary["new_alerts"]] == ["envVars", "highCVE"]
738+
assert [row["type"] for row in strict["new_alerts"]] == [
739+
"envVars",
740+
"highCVE",
741+
"",
742+
]
743+
assert ordinary["new_alerts"][0]["info_url"] == (
744+
"https://socket.dev/alerts/envVars"
745+
)
746+
assert "url" not in ordinary["new_alerts"][1]
747+
assert ordinary["new_alerts"][1]["package_url"] is None
748+
assert ordinary["new_alerts"][1]["info_url"] == (
749+
"https://socket.dev/alerts/highCVE"
750+
)
751+
assert strict["new_alerts"][2]["package_url"] is None
752+
assert strict["new_alerts"][2]["info_url"] is None
753+
assert ordinary["scan_failed"] is False
754+
assert strict["scan_failed"] is False
755+
assert build_socket_json_report(_diff(), _config())["new_alerts"] == []

0 commit comments

Comments
 (0)