Repository: DataDog/guarddog
Pull request #268 (2023-07-19 19:50:31)

Fixed Sometimes Failing PyPI potentially_compromised_email_domain
In response to this [issue](https://github.com/DataDog/guarddog/issues/258), fixed the sometimes failing PyPI rule `potentially_compromised_email_domain`. 

The rule sometimes fails because the email address stored in `package_info` can be `None` and there is no check that the email_address is `None` before doing any parsing. Added a simple `None` check before returning email addresses for npm and python. Added a test for a None or null email


https://datadoghq.atlassian.net/browse/SINT-1432


Patch:
diff --git a/guarddog/analyzer/metadata/npm/potentially_compromised_email_domain.py b/guarddog/analyzer/metadata/npm/potentially_compromised_email_domain.py
index e9b1d56..54e8a30 100644
--- a/guarddog/analyzer/metadata/npm/potentially_compromised_email_domain.py
+++ b/guarddog/analyzer/metadata/npm/potentially_compromised_email_domain.py
@@ -16,7 +16,10 @@ class NPMPotentiallyCompromisedEmailDomainDetector(PotentiallyCompromisedEmailDo
         super().__init__("npm")
 
     def get_email_addresses(self, package_info: dict) -> list[str]:
-        return list(map(lambda x: x["email"], package_info["maintainers"]))
+        if package_info.get("maintainers"):
+            return list(map(lambda x: x["email"], package_info["maintainers"]))
+        else:
+            return []
 
     def get_project_latest_release_date(self, package_info) -> Optional[datetime]:
         """
diff --git a/guarddog/analyzer/metadata/pypi/potentially_compromised_email_domain.py b/guarddog/analyzer/metadata/pypi/potentially_compromised_email_domain.py
index 7707588..e11a1fd 100644
--- a/guarddog/analyzer/metadata/pypi/potentially_compromised_email_domain.py
+++ b/guarddog/analyzer/metadata/pypi/potentially_compromised_email_domain.py
@@ -19,7 +19,10 @@ class PypiPotentiallyCompromisedEmailDomainDetector(PotentiallyCompromisedEmailD
         author_email = package_info["info"]["author_email"]
         maintainer_email = package_info["info"]["maintainer_email"]
         email = author_email or maintainer_email
-        return [email]
+        if email is not None:
+            return [email]
+        else:
+            return []
 
     def get_project_latest_release_date(self, package_info) -> Optional[datetime]:
         """


Test patch:
diff --git a/tests/analyzer/metadata/test_potentially_compromised_email_domain.py b/tests/analyzer/metadata/test_potentially_compromised_email_domain.py
index 529ac49..01a224f 100644
--- a/tests/analyzer/metadata/test_potentially_compromised_email_domain.py
+++ b/tests/analyzer/metadata/test_potentially_compromised_email_domain.py
@@ -9,7 +9,11 @@ from _pytest.monkeypatch import MonkeyPatch
 
 from guarddog.analyzer.metadata.npm import NPMPotentiallyCompromisedEmailDomainDetector
 from guarddog.analyzer.metadata.pypi import PypiPotentiallyCompromisedEmailDomainDetector
-from tests.analyzer.metadata.resources.sample_project_info import PYPI_PACKAGE_INFO
+from tests.analyzer.metadata.resources.sample_project_info import (
+    PYPI_PACKAGE_INFO,
+    generate_pypi_project_info,
+    generate_npm_project_info
+    )
 
 with open(os.path.join(pathlib.Path(__file__).parent.resolve(), "resources", "npm_data.json"), "r") as file:
     NPM_PACKAGE_INFO = json.load(file)
@@ -55,6 +59,23 @@ class TestCompromisedEmail:
         compromised, _ = pypi_detector.detect(PYPI_PACKAGE_INFO)
         assert compromised
 
+    empty_author_pypi = generate_pypi_project_info("author_email", None)
+    empty_author_npm = generate_npm_project_info("maintainters", [{
+        "name": "john doe",
+        "email": None
+    }])
+    
+
+    @pytest.mark.parametrize("package_info, detector",
+                             [(empty_author_pypi, pypi_detector), (empty_author_npm, npm_detector)])
+    def test_email_domain_none(self, package_info, detector):
+        def mock_whois(domain):
+            return MockWhoIs(datetime(1990, 1, 31))
+        
+        MonkeyPatch().setattr("whois.whois", mock_whois)
+        compromised, _ = detector.detect(package_info)
+        assert not compromised
+
     def test_single_package_version(self):
         """
         Regression test for https://github.com/DataDog/guarddog/issues/190