From 9c7afd64c5583a6b46f26a7047ecf45f06f02afa Mon Sep 17 00:00:00 2001 From: StylusFrost Date: Mon, 1 Jun 2026 19:51:27 +0200 Subject: [PATCH] fix(sdk): match compliance provider segment exactly in get_bulk - Compare the module's last dotted segment instead of substring - Prevent a provider name from capturing overlapping built-ins - Add parametrized regression test (cloud, git, work, open cases) - Update get_bulk test mock to the real dotted module name --- prowler/lib/check/compliance_models.py | 4 +- tests/lib/check/compliance_check_test.py | 4 +- .../external/test_dynamic_provider_loading.py | 57 +++++++++++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/prowler/lib/check/compliance_models.py b/prowler/lib/check/compliance_models.py index 86404e7030..8cc588cc4c 100644 --- a/prowler/lib/check/compliance_models.py +++ b/prowler/lib/check/compliance_models.py @@ -438,7 +438,9 @@ class Compliance(BaseModel): # Built-in compliance from prowler/compliance/{provider}/ available_compliance_framework_modules = list_compliance_modules() for compliance_framework in available_compliance_framework_modules: - if provider in compliance_framework.name: + # Match the provider segment exactly, not as a substring, so + # e.g. `cloud` does not capture `cloudflare`. + if compliance_framework.name.split(".")[-1] == provider: compliance_specification_dir_path = ( f"{compliance_framework.module_finder.path}/{provider}" ) diff --git a/tests/lib/check/compliance_check_test.py b/tests/lib/check/compliance_check_test.py index 73f93e3b38..631c134302 100644 --- a/tests/lib/check/compliance_check_test.py +++ b/tests/lib/check/compliance_check_test.py @@ -540,7 +540,9 @@ class TestCompliance: ): object = mock.Mock() object.path = "/path/to/compliance" - object.name = "framework1_aws" + # list_compliance_modules yields dotted module names; get_bulk matches + # the last segment exactly against the provider. + object.name = "prowler.compliance.aws" mock_list_modules.return_value = [object] mock_listdir.return_value = ["framework1_aws.json"] diff --git a/tests/providers/external/test_dynamic_provider_loading.py b/tests/providers/external/test_dynamic_provider_loading.py index ea83b7a6ee..749b7aff95 100644 --- a/tests/providers/external/test_dynamic_provider_loading.py +++ b/tests/providers/external/test_dynamic_provider_loading.py @@ -1355,6 +1355,63 @@ class TestCompliance: assert "dup_framework" in bulk + @pytest.mark.parametrize( + "provider, framework_segments", + [ + # `cloud` is a substring of THREE built-in modules at once. + ("cloud", ["alibabacloud", "cloudflare", "oraclecloud"]), + ("git", ["github"]), + ("work", ["googleworkspace"]), + ("open", ["openstack"]), + ], + ) + @patch("prowler.lib.check.compliance_models.importlib.metadata.entry_points") + @patch("prowler.lib.check.compliance_models.list_compliance_modules") + def test_compliance_get_bulk_matches_provider_segment_exactly( + self, mock_list_modules, mock_ep, provider, framework_segments + ): + """Regression: a provider whose name is a substring of one or more + framework modules must NOT load them. The old `provider in name` + check captured overlapping built-ins (e.g. `cloud` matched + alibabacloud, cloudflare and oraclecloud). See PR #10700 review + (Alan-TheGentleman). + """ + import json + import os + import tempfile + + from prowler.lib.check.compliance_models import Compliance + + mock_ep.return_value = [] + + with tempfile.TemporaryDirectory() as tmpdir: + # The substring path the old code would have read from. + os.mkdir(os.path.join(tmpdir, provider)) + json_data = { + "Framework": "Custom", + "Name": f"Should not load for '{provider}'", + "Version": "1.0", + "Provider": provider, + "Description": "Test", + "Requirements": [], + } + with open(os.path.join(tmpdir, provider, "wrong.json"), "w") as f: + json.dump(json_data, f) + + modules = [] + for segment in framework_segments: + module = MagicMock() + module.name = f"prowler.compliance.{segment}" + module.module_finder.path = tmpdir + modules.append(module) + mock_list_modules.return_value = modules + + bulk = Compliance.get_bulk(provider) + + # Exact-segment match: the provider is not any of these modules. + assert "wrong" not in bulk + assert bulk == {} + # =========================================================================== # 7. Parser