From 27d5c4a75eea36ab12eee36040272ac2a123a5eb Mon Sep 17 00:00:00 2001 From: xxiaoxiong <2482929840@qq.com> Date: Thu, 7 May 2026 12:18:11 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=BA=E8=BF=9C=E7=A8=8B=20PyPI=20?= =?UTF-8?q?=E6=89=AB=E6=8F=8F=E6=B7=BB=E5=8A=A0=20PyPI=20Inspector=20?= =?UTF-8?q?=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 当扫描远程 PyPI 包并发现问题时,在输出中显示 PyPI Inspector 链接。 修改内容: - cli.py: 为远程扫描添加 is_remote、ecosystem 和 scanned_version 标记 - human_readable.py: 在有问题时显示 PyPI Inspector 链接 - test_human_readable.py: 添加 5 个测试用例验证功能 Fixes #67 --- guarddog/cli.py | 13 +++++ guarddog/reporters/human_readable.py | 10 ++++ tests/reporters/test_human_readable.py | 76 ++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) diff --git a/guarddog/cli.py b/guarddog/cli.py index e3793c561..60f6920f1 100644 --- a/guarddog/cli.py +++ b/guarddog/cli.py @@ -218,6 +218,19 @@ def _scan( else: log.debug(f"Considering that '{identifier}' is a remote target") result |= scanner.scan_remote(identifier, version, rule_param) + + scanned_version = version + if version is None and ecosystem == ECOSYSTEM.PYPI: + from guarddog.utils.package_info import get_package_info + try: + package_info = get_package_info(identifier) + scanned_version = package_info["info"]["version"] + except Exception: + pass + + result["is_remote"] = True + result["ecosystem"] = ecosystem + result["scanned_version"] = scanned_version except Exception as e: log.error(f"Error occurred while scanning target {identifier}: '{e}'\n") sys.exit(1) diff --git a/guarddog/reporters/human_readable.py b/guarddog/reporters/human_readable.py index 3d2423d19..27f655048 100644 --- a/guarddog/reporters/human_readable.py +++ b/guarddog/reporters/human_readable.py @@ -110,6 +110,16 @@ def _format_code_line_for_output(code) -> str: ) lines.append("") + if ( + num_issues > 0 + and results.get("is_remote") + and results.get("ecosystem") == ECOSYSTEM.PYPI + ): + scanned_version = results.get("scanned_version") + if scanned_version: + inspector_url = f"https://inspector.pypi.io/project/{safe_identifier}/{scanned_version}" + lines.append(f"For more details, see: {inspector_url}") + return "\n".join(lines) @staticmethod diff --git a/tests/reporters/test_human_readable.py b/tests/reporters/test_human_readable.py index d06e6b778..3a0ead0a6 100644 --- a/tests/reporters/test_human_readable.py +++ b/tests/reporters/test_human_readable.py @@ -1,6 +1,7 @@ import re from guarddog.reporters.human_readable import HumanReadableReporter, _sanitize +from guarddog.ecosystems import ECOSYSTEM # Strips ANSI SGR/CSI/OSC sequences emitted by termcolor so assertions can match # the underlying text instead of color codes. @@ -157,3 +158,78 @@ def test_print_scan_results_benign_input_is_preserved(): assert "matched a benign-looking pattern" in plain assert "requests" in plain assert "rule-name" in plain + + +def test_pypi_inspector_link_shown_for_remote_pypi_with_issues(): + results = { + "issues": 1, + "errors": {}, + "results": { + "some-rule": "found suspicious behavior" + }, + "is_remote": True, + "ecosystem": ECOSYSTEM.PYPI, + "scanned_version": "2.28.1" + } + out = HumanReadableReporter.print_scan_results("requests", results) + plain = _strip_color(out) + assert "https://inspector.pypi.io/project/requests/2.28.1" in plain + + +def test_pypi_inspector_link_not_shown_for_local_scan(): + results = { + "issues": 1, + "errors": {}, + "results": { + "some-rule": "found suspicious behavior" + } + } + out = HumanReadableReporter.print_scan_results("requests", results) + plain = _strip_color(out) + assert "inspector.pypi.io" not in plain + + +def test_pypi_inspector_link_not_shown_when_no_issues(): + results = { + "issues": 0, + "errors": {}, + "results": {}, + "is_remote": True, + "ecosystem": ECOSYSTEM.PYPI, + "scanned_version": "2.28.1" + } + out = HumanReadableReporter.print_scan_results("requests", results) + plain = _strip_color(out) + assert "inspector.pypi.io" not in plain + + +def test_pypi_inspector_link_not_shown_for_npm(): + results = { + "issues": 1, + "errors": {}, + "results": { + "some-rule": "found suspicious behavior" + }, + "is_remote": True, + "ecosystem": ECOSYSTEM.NPM, + "scanned_version": "1.0.0" + } + out = HumanReadableReporter.print_scan_results("lodash", results) + plain = _strip_color(out) + assert "inspector.pypi.io" not in plain + + +def test_pypi_inspector_link_not_shown_when_version_is_none(): + results = { + "issues": 1, + "errors": {}, + "results": { + "some-rule": "found suspicious behavior" + }, + "is_remote": True, + "ecosystem": ECOSYSTEM.PYPI, + "scanned_version": None + } + out = HumanReadableReporter.print_scan_results("requests", results) + plain = _strip_color(out) + assert "inspector.pypi.io" not in plain