The entry-point loop in get_available_compliance_frameworks appended
framework names without checking whether the same name was already
provided by a built-in. The loader (Compliance.get_bulk) already
respects "built-in wins" via an explicit guard, but the listing
function did not, causing --list-compliance and
--list-compliance-requirements to print the same name twice on
collision with a built-in.
Adds the same dedup guard already used by the universal frameworks
loop a few lines above, restoring symmetry across the three
compliance discovery layers.
Includes a regression test that registers a fake entry point with
a name colliding with a known built-in (cis_2.0_aws) and asserts
the framework appears exactly once in the resulting list.
Two unrelated lint blockers that CI surfaced once the file was in scope
of `black --check .` and `vulture` on the changed-file path:
- Reformat one if/and conditional that black wanted on a single line.
- Underscore-prefix unused parameter names on FakeProvider stub methods
(from_cli_args, get_output_options, display_compliance_table,
generate_compliance_output, FakePureContractProvider.from_cli_args)
and the throwaway **kw in a MagicMock side_effect lambda. These are
test fixtures whose method bodies don't reference those args; the
signatures must match the abstract contract on Provider, so we keep
positions/types and just rename to indicate intentional non-use.
No logic change.
Calling importlib.util.find_spec on prowler.providers.{provider}.services
for an external provider propagates ModuleNotFoundError when the parent
package prowler.providers.{provider} does not exist, instead of returning
None. This caused recover_checks_from_provider, _resolve_check_module and
Scan.scan to fail with "No module named 'prowler.providers.{external}'"
even though the plug-in registered its checks via entry points correctly.
Gate the built-in branch on Provider.is_builtin (which already wraps the
find_spec in try/except) and reuse _resolve_check_module from Scan.scan
so external providers fall through to the entry-point lookup.
Conflict in prowler/config/config.py resolved by combining both branches:
- HEAD: external compliance discovery via entry points (PROWLER-1391)
- master: multi-provider framework JSONs scanned at top-level compliance/ (#10300)
Order: built-in per-provider -> built-in multi-provider -> external entry points.
Built-ins first so they win on name collisions against external registrations.
Supporting external plug-ins to register multi-provider frameworks is tracked
in PROWLER-1444.
load_and_validate_config_file only detected the namespaced format for 5
hardcoded providers (aws, gcp, azure, kubernetes, m365). For every other
built-in (github, nhn, vercel, cloudflare, iac, llm, image, mongodbatlas,
oraclecloud, openstack, alibabacloud, googleworkspace) and for any
external plug-in, the full YAML was returned wrapped instead of the
provider's own block.
Replace the hardcoded list with a dynamic check: if the file has a
top-level key matching the provider and its value is a dict, unwrap it.
Keep the legacy flat format for AWS only (historical, pre-multicloud)
and identify it by the absence of nested-dict top-level values, which
prevents cross-provider config leakage when a namespaced file has no
section for the requested provider.