From 628de4bd06d5d303f11ffa3a25d35edeb9b9674a Mon Sep 17 00:00:00 2001 From: Prowler Bot Date: Thu, 16 Apr 2026 13:31:08 +0200 Subject: [PATCH] fix(image): --registry-list crashes with AttributeError on global_provider (#10730) Co-authored-by: Erich Blume <725328+eblume@users.noreply.github.com> Co-authored-by: Andoni A. <14891798+andoniaf@users.noreply.github.com> --- prowler/CHANGELOG.md | 2 +- prowler/__main__.py | 4 ++ prowler/providers/image/image_provider.py | 74 +++++++++++--------- tests/providers/image/image_provider_test.py | 55 +++++++++++++++ 4 files changed, 101 insertions(+), 34 deletions(-) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 991ac60a0b..071ea5088e 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to the **Prowler SDK** are documented in this file. ### 🐞 Fixed - Cloudflare account-scoped API tokens failing connection test in the App with `CloudflareUserTokenRequiredError` [(#10723)](https://github.com/prowler-cloud/prowler/pull/10723) +- `prowler image --registry` failing with `ImageNoImagesProvidedError` due to registry arguments not being forwarded to `ImageProvider` in `init_global_provider` [(#10470)](https://github.com/prowler-cloud/prowler/pull/10470) --- @@ -85,7 +86,6 @@ All notable changes to the **Prowler SDK** are documented in this file. - Oracle Cloud `kms_key_rotation_enabled` now checks current key version age to avoid false positives on vaults without auto-rotation support [(#10450)](https://github.com/prowler-cloud/prowler/pull/10450) - OCI filestorage, blockstorage, KMS, and compute services now honor `--region` for scanning outside the tenancy home region [(#10472)](https://github.com/prowler-cloud/prowler/pull/10472) - OCI provider now supports multi-region filtering via `--region` [(#10473)](https://github.com/prowler-cloud/prowler/pull/10473) -- `prowler image --registry` failing with `ImageNoImagesProvidedError` due to registry arguments not being forwarded to `ImageProvider` in `init_global_provider` [(#10470)](https://github.com/prowler-cloud/prowler/pull/10470) - OCI multi-region support for identity client configuration in blockstorage, identity, and filestorage services [(#10520)](https://github.com/prowler-cloud/prowler/pull/10520) - Google Workspace Calendar checks now filter for customer-level policies only, skipping OU and group overrides that could produce incorrect audit results [(#10658)](https://github.com/prowler-cloud/prowler/pull/10658) diff --git a/prowler/__main__.py b/prowler/__main__.py index a704be9aab..8de091328b 100644 --- a/prowler/__main__.py +++ b/prowler/__main__.py @@ -293,6 +293,10 @@ def prowler(): if not args.only_logs: global_provider.print_credentials() + # --registry-list: listing already printed during provider init, exit + if getattr(global_provider, "_listing_only", False): + sys.exit() + # Skip service and check loading for external-tool providers if provider not in EXTERNAL_TOOL_PROVIDERS: # Import custom checks from folder diff --git a/prowler/providers/image/image_provider.py b/prowler/providers/image/image_provider.py index d724542e28..7c9a9cbb08 100644 --- a/prowler/providers/image/image_provider.py +++ b/prowler/providers/image/image_provider.py @@ -163,42 +163,50 @@ class ImageProvider(Provider): # Registry scan mode: enumerate images from registry if self.registry: self._enumerate_registry() - if self._listing_only: - return - for image in self.images: - self._validate_image_name(image) - - if not self.images: - raise ImageNoImagesProvidedError( - file=__file__, - message="No images provided for scanning.", - ) - - # Audit Config - if config_content: - self._audit_config = config_content - else: - if not config_path: - config_path = default_config_file_path - self._audit_config = load_and_validate_config_file(self._type, config_path) - - # Fixer Config - self._fixer_config = fixer_config if fixer_config is not None else {} - - # Mutelist (not needed for Image provider since Trivy has its own logic) + # Safe defaults for listing-only mode (overwritten below in scan mode) + self._audit_config = {} + self._fixer_config = {} self._mutelist = None + self.audit_metadata = None - self.audit_metadata = Audit_Metadata( - provider=self._type, - account_id=self.audited_account, - account_name="image", - region=self.region, - services_scanned=0, - expected_checks=[], - completed_checks=0, - audit_progress=0, - ) + # Skip scan setup for listing-only mode + if not self._listing_only: + for image in self.images: + self._validate_image_name(image) + + if not self.images: + raise ImageNoImagesProvidedError( + file=__file__, + message="No images provided for scanning.", + ) + + # Audit Config + if config_content: + self._audit_config = config_content + else: + if not config_path: + config_path = default_config_file_path + self._audit_config = load_and_validate_config_file( + self._type, config_path + ) + + # Fixer Config + self._fixer_config = fixer_config if fixer_config is not None else {} + + # Mutelist (not needed for Image provider since Trivy has its own logic) + self._mutelist = None + + self.audit_metadata = Audit_Metadata( + provider=self._type, + account_id=self.audited_account, + account_name="image", + region=self.region, + services_scanned=0, + expected_checks=[], + completed_checks=0, + audit_progress=0, + ) Provider.set_global_provider(self) diff --git a/tests/providers/image/image_provider_test.py b/tests/providers/image/image_provider_test.py index 941685f662..68b0c5d7a2 100644 --- a/tests/providers/image/image_provider_test.py +++ b/tests/providers/image/image_provider_test.py @@ -1185,3 +1185,58 @@ class TestInitGlobalProviderRegistryEnumeration: # The "other/lib" repo should be filtered out by --image-filter assert not any("other/lib" in img for img in provider.images) assert len(provider.images) == 3 + + +class TestRegistryListMode: + """Regression test: `prowler image --registry --registry-list` crashes. + + When --registry-list is passed, ImageProvider._enumerate_registry sets + _listing_only = True and __init__ returns early — before calling + Provider.set_global_provider(self). The caller in __main__.py then calls + global_provider.print_credentials() on a None reference, raising + AttributeError: 'NoneType' object has no attribute 'print_credentials'. + """ + + @patch("prowler.providers.image.image_provider.create_registry_adapter") + @patch("prowler.providers.common.provider.load_and_validate_config_file") + def test_registry_list_does_not_crash(self, mock_load_config, mock_adapter_factory): + """Reproduce the --registry-list crash by running the same sequence + as __main__.py: init_global_provider, get_global_provider, + then print_credentials.""" + mock_load_config.return_value = {} + + adapter = MagicMock() + adapter.list_repositories.return_value = ["myorg/app"] + adapter.list_tags.return_value = ["v1.0", "latest"] + mock_adapter_factory.return_value = adapter + + arguments = Namespace( + provider="image", + config_file=None, + fixer_config=None, + images=None, + image_list_file=None, + scanners=["vuln"], + image_config_scanners=None, + trivy_severity=None, + ignore_unfixed=False, + timeout="5m", + registry="myregistry.io", + image_filter=None, + tag_filter=None, + max_images=0, + registry_insecure=False, + registry_list_images=True, + ) + + # Reproduce the exact crash sequence from __main__.py lines 289-294: + # Provider.init_global_provider(args) + # global_provider = Provider.get_global_provider() + # global_provider.print_credentials() + with mock.patch.object(Provider, "_global", None): + Provider.init_global_provider(arguments) + global_provider = Provider.get_global_provider() + + # This is the line that crashes: global_provider is None so + # .print_credentials() raises AttributeError. + global_provider.print_credentials()