diff --git a/.env b/.env index 60c80b1363..c6085538a0 100644 --- a/.env +++ b/.env @@ -145,7 +145,7 @@ SENTRY_RELEASE=local NEXT_PUBLIC_SENTRY_ENVIRONMENT=${SENTRY_ENVIRONMENT} #### Prowler release version #### -NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.29.0 +NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.30.0 # Social login credentials SOCIAL_GOOGLE_OAUTH_CALLBACK_URL="${AUTH_URL}/api/auth/callback/google" diff --git a/api/pyproject.toml b/api/pyproject.toml index 6d4c452188..5d996878fa 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -68,7 +68,7 @@ name = "prowler-api" package-mode = false # Needed for the SDK compatibility requires-python = ">=3.11,<3.13" -version = "1.30.0" +version = "1.31.0" [tool.uv] # Transitive pins matching master to avoid silent drift; bump deliberately. diff --git a/api/src/backend/api/filters.py b/api/src/backend/api/filters.py index 10edc304fd..92db0ddf90 100644 --- a/api/src/backend/api/filters.py +++ b/api/src/backend/api/filters.py @@ -1,5 +1,4 @@ from datetime import date, datetime, timedelta, timezone -from functools import lru_cache from dateutil.parser import parse from django.conf import settings @@ -67,19 +66,8 @@ from api.uuid_utils import ( uuid7_range, uuid7_start, ) +from api.provider_types import get_provider_type_choices from api.v1.serializers import TaskBase -from prowler.providers.common.provider import Provider as SDKProvider - - -@lru_cache(maxsize=1) -def get_provider_type_choices(): - """Provider-type filter choices driven by the SDK's available providers - instead of a static enum, so filtering covers external providers too. - - Cached because the installed providers are fixed for the process lifetime - and provider-type filters live on hot list endpoints. - """ - return [(name, name) for name in SDKProvider.get_available_providers()] class CustomDjangoFilterBackend(DjangoFilterBackend): diff --git a/api/src/backend/api/migrations/0095_backfill_provider_str.py b/api/src/backend/api/migrations/0095_backfill_provider_str.py index 09906d899c..70a7ce04d0 100644 --- a/api/src/backend/api/migrations/0095_backfill_provider_str.py +++ b/api/src/backend/api/migrations/0095_backfill_provider_str.py @@ -1,31 +1,25 @@ from django.db import migrations -from tasks.tasks import backfill_provider_str_task - -from api.db_router import MainRouter - - -def trigger_provider_str_backfill(apps, _schema_editor): - """Dispatch a per-tenant Celery task to populate the transitional - `provider_str` shadow column for rows created before the sync trigger - from 0094 existed. - - New writes are already covered by the trigger, so this only fills the gap - left by pre-existing rows. The work runs in the background, batched per - tenant, so the migration itself finishes in seconds regardless of table - size. - """ - Tenant = apps.get_model("api", "Tenant") - tenant_ids = Tenant.objects.using(MainRouter.admin_db).values_list("id", flat=True) - - for tenant_id in tenant_ids: - backfill_provider_str_task.delay(tenant_id=str(tenant_id)) class Migration(migrations.Migration): + """Synchronous backfill of the `provider_str` shadow column. + + A single UPDATE fills rows that predate the 0094 trigger. The providers + table is small, so this is safe inline and guarantees the column is fully + populated before 0096 sets it NOT NULL (no race with an async backfill). + Runs on the migration connection, which is exempt from RLS. + """ + dependencies = [ ("api", "0094_provider_str_shadow_column"), ] operations = [ - migrations.RunPython(trigger_provider_str_backfill, migrations.RunPython.noop), + migrations.RunSQL( + sql=( + "UPDATE providers SET provider_str = provider::text " + "WHERE provider_str IS NULL;" + ), + reverse_sql=migrations.RunSQL.noop, + ), ] diff --git a/api/src/backend/api/migrations/0096_provider_enum_to_varchar_contract.py b/api/src/backend/api/migrations/0096_provider_enum_to_varchar_contract.py index 0f8bc9a6cf..7e327706de 100644 --- a/api/src/backend/api/migrations/0096_provider_enum_to_varchar_contract.py +++ b/api/src/backend/api/migrations/0096_provider_enum_to_varchar_contract.py @@ -2,20 +2,14 @@ from django.db import migrations, models class Migration(migrations.Migration): - """Contract step of the zero-downtime migration of Provider.provider from a - native PostgreSQL enum to varchar. + """Contract step: promote `provider_str` into `provider`. - The shadow column added in 0094 has been kept in sync by the trigger and - backfilled in 0095, so it now holds the value for every row. This migration - promotes it into place: drop the trigger and the enum column, rename the - shadow column to `provider`, and drop the orphaned enum type. The column - name is preserved throughout, and varchar accepts the same string values - the enum held, so app instances running the previous release keep working - against the swapped column. - - The drop/rename runs in this migration's transaction so `provider` never - disappears for readers. The partial unique index is dropped here and - rebuilt concurrently in the next migration to avoid a long write lock. + Drops the trigger and enum column, renames the shadow column, sets it NOT + NULL, and drops the enum type. The unique index is dropped and recreated in + the same transaction, so there is no window for duplicate active providers; + recreated non-concurrently since the table is small, with a short + lock_timeout so the migration fails fast instead of queueing behind a + long-running transaction. """ dependencies = [ @@ -38,6 +32,7 @@ class Migration(migrations.Migration): database_operations=[ migrations.RunSQL( sql=( + "SET LOCAL lock_timeout = '10s';\n" "DROP TRIGGER IF EXISTS providers_sync_provider_str ON providers;\n" "DROP FUNCTION IF EXISTS sync_provider_str();\n" "DROP INDEX IF EXISTS unique_provider_uids;\n" @@ -45,7 +40,9 @@ class Migration(migrations.Migration): "ALTER TABLE providers RENAME COLUMN provider_str TO provider;\n" "ALTER TABLE providers ALTER COLUMN provider SET DEFAULT 'aws';\n" "ALTER TABLE providers ALTER COLUMN provider SET NOT NULL;\n" - "DROP TYPE provider;" + "DROP TYPE provider;\n" + "CREATE UNIQUE INDEX unique_provider_uids ON providers " + "(tenant_id, provider, uid) WHERE NOT is_deleted;" ), reverse_sql=migrations.RunSQL.noop, ), diff --git a/api/src/backend/api/migrations/0097_provider_unique_index_concurrent.py b/api/src/backend/api/migrations/0097_provider_unique_index_concurrent.py deleted file mode 100644 index 94fdef6e4c..0000000000 --- a/api/src/backend/api/migrations/0097_provider_unique_index_concurrent.py +++ /dev/null @@ -1,28 +0,0 @@ -from django.db import migrations - - -class Migration(migrations.Migration): - """Rebuild the partial unique index on `providers` after the enum-to-varchar - contract in 0096 dropped it along with the old enum column. - - Built with CREATE INDEX CONCURRENTLY (hence `atomic = False`) so the rebuild - holds no long write lock on a large table. The index keeps the name and - predicate Django expects for the existing `unique_provider_uids` constraint, - which stays in the model state untouched, so no state operation is needed. - """ - - atomic = False - - dependencies = [ - ("api", "0096_provider_enum_to_varchar_contract"), - ] - - operations = [ - migrations.RunSQL( - sql=( - "CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS unique_provider_uids " - "ON providers (tenant_id, provider, uid) WHERE NOT is_deleted;" - ), - reverse_sql="DROP INDEX CONCURRENTLY IF EXISTS unique_provider_uids;", - ), - ] diff --git a/api/src/backend/api/provider_types.py b/api/src/backend/api/provider_types.py new file mode 100644 index 0000000000..ce909d59d6 --- /dev/null +++ b/api/src/backend/api/provider_types.py @@ -0,0 +1,15 @@ +from functools import lru_cache + +from prowler.providers.common.provider import Provider as SDKProvider + + +@lru_cache(maxsize=1) +def get_provider_type_choices(): + """Provider-type choices from the SDK's available providers, so they cover + external providers and not just a static enum. + + Cached for the process lifetime; hot-installing a provider needs + coordinated cache invalidation (tracked separately) to show up here without + a restart. Shared by the filters and the provider serializer. + """ + return [(name, name) for name in SDKProvider.get_available_providers()] diff --git a/api/src/backend/api/specs/v1.yaml b/api/src/backend/api/specs/v1.yaml index c4f8c969dd..5efefc0790 100644 --- a/api/src/backend/api/specs/v1.yaml +++ b/api/src/backend/api/specs/v1.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: Prowler API - version: 1.30.0 + version: 1.31.0 description: |- Prowler API specification. diff --git a/api/src/backend/api/v1/serializers.py b/api/src/backend/api/v1/serializers.py index 693450ce2c..5d71d2c2e2 100644 --- a/api/src/backend/api/v1/serializers.py +++ b/api/src/backend/api/v1/serializers.py @@ -72,6 +72,7 @@ from api.v1.serializer_utils.lighthouse import ( OpenAICredentialsSerializer, ) from api.v1.serializer_utils.processors import ProcessorConfigField +from api.provider_types import get_provider_type_choices from api.v1.serializer_utils.providers import ProviderSecretField from prowler.lib.mutelist.mutelist import Mutelist from prowler.providers.common.provider import Provider as SDKProvider @@ -856,12 +857,9 @@ class ProviderGroupMembershipSerializer(RLSSerializer, BaseWriteSerializer): # Providers class ProviderEnumSerializerField(serializers.ChoiceField): def __init__(self, **kwargs): - # The SDK is the source of truth for which providers exist, so the - # accepted values track the installed providers (built-in or external) - # instead of a static enum. - kwargs["choices"] = [ - (name, name) for name in SDKProvider.get_available_providers() - ] + # Accepted values track the SDK's installed providers (built-in or + # external), shared with the filters via one cached source. + kwargs["choices"] = get_provider_type_choices() super().__init__(**kwargs) diff --git a/api/src/backend/tasks/jobs/backfill.py b/api/src/backend/tasks/jobs/backfill.py index 27a2fa7004..825dcb7ca8 100644 --- a/api/src/backend/tasks/jobs/backfill.py +++ b/api/src/backend/tasks/jobs/backfill.py @@ -40,35 +40,6 @@ from api.models import ( logger = get_task_logger(__name__) -def backfill_provider_str(tenant_id: str, batch_size: int = 1000): - """Populate the transitional `provider_str` shadow column for rows that - predate the sync trigger, copying `provider::text` in bounded batches. - - Each batch runs in its own RLS transaction so the lock is held only for the - rows in that batch, keeping the operation safe on a large table. Idempotent: - only rows where `provider_str IS NULL` are touched, so an interrupted run - resumes cleanly and a completed column is a no-op on retry. - """ - total_updated = 0 - while True: - with rls_transaction(tenant_id) as cursor: - cursor.execute( - "UPDATE providers SET provider_str = provider::text " - "WHERE id IN (" - " SELECT id FROM providers WHERE provider_str IS NULL LIMIT %s" - ")", - [batch_size], - ) - updated = cursor.rowcount - total_updated += updated - if updated < batch_size: - break - logger.info( - "Backfilled provider_str for tenant %s: %d rows", tenant_id, total_updated - ) - return {"tenant_id": tenant_id, "updated": total_updated} - - def backfill_resource_scan_summaries(tenant_id: str, scan_id: str): with rls_transaction(tenant_id, using=READ_REPLICA_ALIAS): if ResourceScanSummary.objects.filter( diff --git a/api/src/backend/tasks/tasks.py b/api/src/backend/tasks/tasks.py index 61a925c0ed..0fda2999a9 100644 --- a/api/src/backend/tasks/tasks.py +++ b/api/src/backend/tasks/tasks.py @@ -19,7 +19,6 @@ from tasks.jobs.backfill import ( backfill_daily_severity_summaries, backfill_finding_group_summaries, backfill_provider_compliance_scores, - backfill_provider_str, backfill_resource_scan_summaries, aggregate_scan_category_summaries, aggregate_scan_resource_group_summaries, @@ -723,12 +722,6 @@ def backfill_finding_group_summaries_task(tenant_id: str, days: int = None): return backfill_finding_group_summaries(tenant_id=tenant_id, days=days) -@shared_task(name="backfill-provider-str", queue="backfill") -def backfill_provider_str_task(tenant_id: str): - """Backfill the transitional provider_str shadow column for a tenant.""" - return backfill_provider_str(tenant_id=tenant_id) - - @shared_task(name="scan-category-summaries", queue="overview") @handle_provider_deletion def aggregate_scan_category_summaries_task(tenant_id: str, scan_id: str): diff --git a/api/uv.lock b/api/uv.lock index 57dd1a388d..d90eff5fdf 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -4494,7 +4494,7 @@ dependencies = [ [[package]] name = "prowler-api" -version = "1.30.0" +version = "1.31.0" source = { virtual = "." } dependencies = [ { name = "cartography" }, diff --git a/docs/getting-started/installation/prowler-app.mdx b/docs/getting-started/installation/prowler-app.mdx index 52d095d581..3f19329914 100644 --- a/docs/getting-started/installation/prowler-app.mdx +++ b/docs/getting-started/installation/prowler-app.mdx @@ -118,8 +118,8 @@ To update the environment file: Edit the `.env` file and change version values: ```env -PROWLER_UI_VERSION="5.28.0" -PROWLER_API_VERSION="5.28.0" +PROWLER_UI_VERSION="5.29.0" +PROWLER_API_VERSION="5.29.0" ``` diff --git a/docs/getting-started/installation/prowler-cli.mdx b/docs/getting-started/installation/prowler-cli.mdx index 2c79eda37c..f87653dcfe 100644 --- a/docs/getting-started/installation/prowler-cli.mdx +++ b/docs/getting-started/installation/prowler-cli.mdx @@ -40,12 +40,6 @@ To install Prowler as a Python package, use `Python >= 3.10, <= 3.12`. Prowler i pip install prowler prowler -v ``` - - To upgrade Prowler to the latest version: - - ``` bash - pip install --upgrade prowler - ``` _Requirements_: @@ -170,6 +164,68 @@ To install Prowler as a Python package, use `Python >= 3.10, <= 3.12`. Prowler i +## Updating Prowler CLI + +Upgrade Prowler CLI to the latest release using the same method chosen for installation: + + + + ```bash + pipx upgrade prowler + prowler -v + ``` + + + ```bash + pip install --upgrade prowler + prowler -v + ``` + + + Pull the desired image tag to fetch the latest version: + + ```bash + docker pull toniblyx/prowler:latest + ``` + + + Replace `latest` with a specific release tag (for example, `stable` or ``) to pin a version. Refer to the [Container Versions](#container-versions) section for the full list of available tags. + + + + Pull the latest changes and sync the environment: + + ```bash + cd prowler + git pull + uv sync + uv run python prowler-cli.py -v + ``` + + + To upgrade to a specific release, check out the corresponding tag before syncing: `git checkout `. + + + + ```bash + brew upgrade prowler + prowler -v + ``` + + + Both AWS CloudShell and Azure CloudShell install Prowler with `pipx`, so the upgrade command is the same: + + ```bash + pipx upgrade prowler + prowler -v + ``` + + + + + To install a specific version instead of the latest release, pin it explicitly. For example, with `pipx`: `pipx install prowler==`, or with `pip`: `pip install prowler==`. The available releases are listed in the [Releases GitHub section](https://github.com/prowler-cloud/prowler/releases). + + ## Container Versions The available versions of Prowler CLI are the following: diff --git a/docs/getting-started/installation/prowler-mcp.mdx b/docs/getting-started/installation/prowler-mcp.mdx index 7bf766e143..06f204da6a 100644 --- a/docs/getting-started/installation/prowler-mcp.mdx +++ b/docs/getting-started/installation/prowler-mcp.mdx @@ -141,6 +141,45 @@ Choose one of the following installation methods: --- +## Updating Prowler MCP Server + +When running Prowler MCP Server locally ("Option 2: Run Locally"), upgrade to the latest version using the same method chosen for installation. The hosted server (`https://mcp.prowler.com/mcp`) is always kept up to date by Prowler and requires no action. + + + + Pull the latest image and restart the container: + + ```bash + docker pull prowlercloud/prowler-mcp + ``` + + + Recreate any running container after pulling the new image so the updated version takes effect. + + + + Pull the latest changes and sync the dependencies: + + ```bash + cd prowler/mcp_server + git pull + uv sync + uv run prowler-mcp --help + ``` + + + Pull the latest source and rebuild the image: + + ```bash + cd prowler/mcp_server + git pull + docker build -t prowler-mcp . + ``` + + + +--- + ## Command Line Options The Prowler MCP Server supports the following command-line arguments: diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index d037135bcb..48ba4d2f4e 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -32,6 +32,7 @@ All notable changes to the **Prowler SDK** are documented in this file. ### 🐞 Fixed +- Broken documentation URLs in Google Workspace check metadata [(#11405)](https://github.com/prowler-cloud/prowler/pull/11405) - ENS RD 311/2022 (AWS) compliance mapping: `vpc_different_regions` was uncorrectly mapped under the `mp.com.4` family (Network segregation). That check is now mapped to a new `op.cont.2.aws.vpc.1` requirement under the Continuity of Service control [(#11372)](https://github.com/prowler-cloud/prowler/pull/11372) - Compliance CSV row count now matches the UI per requirement by sourcing rows from the framework JSON's `requirement.Checks` instead of the stale `finding.compliance` snapshot [(#11370)](https://github.com/prowler-cloud/prowler/pull/11370) - OpenStack provider exception codes moved from the `10000-10999` range, shared with the AlibabaCloud provider, to the free `17000-17999` range to keep error codes unambiguous [(#11382)](https://github.com/prowler-cloud/prowler/pull/11382) diff --git a/prowler/config/config.py b/prowler/config/config.py index fd7b87bffc..10e63c42df 100644 --- a/prowler/config/config.py +++ b/prowler/config/config.py @@ -49,7 +49,7 @@ class _MutableTimestamp: timestamp = _MutableTimestamp(datetime.today()) timestamp_utc = _MutableTimestamp(datetime.now(timezone.utc)) -prowler_version = "5.29.0" +prowler_version = "5.30.0" html_logo_url = "https://github.com/prowler-cloud/prowler/" square_logo_img = "https://raw.githubusercontent.com/prowler-cloud/prowler/dc7d2d5aeb92fdf12e8604f42ef6472cd3e8e889/docs/img/prowler-logo-black.png" aws_logo = "https://user-images.githubusercontent.com/38561120/235953920-3e3fba08-0795-41dc-b480-9bea57db9f2e.png" 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/prowler/lib/check/tool_wrapper.py b/prowler/lib/check/tool_wrapper.py index f00afd35d6..a1d606594e 100644 --- a/prowler/lib/check/tool_wrapper.py +++ b/prowler/lib/check/tool_wrapper.py @@ -13,6 +13,7 @@ and `prowler.providers.common.provider` without forming an import cycle. import importlib.metadata from prowler.lib.check.external_tool_providers import EXTERNAL_TOOL_PROVIDERS +from prowler.providers.common.builtin import is_builtin_provider # Module-level cache for entry-point classes consulted by this helper. # Independent of `Provider._ep_providers` to keep this module leaf — the cost @@ -53,5 +54,9 @@ def is_tool_wrapper_provider(provider: str) -> bool: """ if provider in EXTERNAL_TOOL_PROVIDERS: return True + # Built-in wins: short-circuit before ep.load() so a same-name plug-in + # cannot flip a built-in onto the tool-wrapper path or run its code. + if is_builtin_provider(provider): + return False cls = _load_ep_class(provider) return bool(cls and getattr(cls, "is_external_tool_provider", False)) diff --git a/prowler/providers/common/provider.py b/prowler/providers/common/provider.py index a9bcea1be3..7462df70ad 100644 --- a/prowler/providers/common/provider.py +++ b/prowler/providers/common/provider.py @@ -339,9 +339,11 @@ class Provider(ABC): # plug-in is ignored. This lives here (not in get_class) so # that `prowler --help` and API callers that resolve a class # without initialising a global provider do not see spurious - # warnings. - if Provider.is_builtin(arguments.provider) and ( - Provider._load_ep_provider(arguments.provider) is not None + # warnings. Match by name only — never ep.load() a shadowing + # plug-in, or its module code would run during a built-in run. + if Provider.is_builtin(arguments.provider) and any( + ep.name == arguments.provider + for ep in importlib.metadata.entry_points(group="prowler.providers") ): logger.warning( f"Plug-in provider '{arguments.provider}' registered " @@ -350,13 +352,6 @@ class Provider(ABC): f"it under a different name." ) - # Kept for downstream forks that may extend the dispatch below - # with their own custom built-in branches and reference this name. - # The upstream chain dispatches by `arguments.provider` directly. - provider_class_name = ( # noqa: F841 - f"{arguments.provider.capitalize()}Provider" - ) - fixer_config = load_and_validate_config_file( arguments.provider, arguments.fixer_config ) @@ -737,9 +732,10 @@ class Provider(ABC): def get_class(provider: str) -> type: """Resolve the provider class for a name (built-in or entry-point). - Side-effect-free: no ``sys.exit``, no global state. Collision warnings - are emitted by ``init_global_provider``, not here. The caller handles - errors (CLI exits; the API can return HTTP 400). + Does not call ``sys.exit`` and does not initialize the global + provider (it may populate the ``_ep_providers`` memoization cache). + Collision warnings are emitted by ``init_global_provider``, not here. + The caller handles errors (CLI exits; the API can return HTTP 400). Args: provider: Provider name, e.g. ``"aws"`` or an external plug-in. diff --git a/prowler/providers/googleworkspace/services/additionalservices/additionalservices_external_groups_disabled/additionalservices_external_groups_disabled.metadata.json b/prowler/providers/googleworkspace/services/additionalservices/additionalservices_external_groups_disabled/additionalservices_external_groups_disabled.metadata.json index 82bb368a8a..8dcf6c1f3f 100644 --- a/prowler/providers/googleworkspace/services/additionalservices/additionalservices_external_groups_disabled/additionalservices_external_groups_disabled.metadata.json +++ b/prowler/providers/googleworkspace/services/additionalservices/additionalservices_external_groups_disabled/additionalservices_external_groups_disabled.metadata.json @@ -13,8 +13,8 @@ "Risk": "When external Google Groups access is enabled, users can access and participate in groups created **outside the organization**, potentially exposing them to **phishing, social engineering, or data leakage** through unmanaged external group communications.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/181865", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/users/advanced/turn-on-or-off-additional-google-services", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/calendar/calendar_external_invitations_warning/calendar_external_invitations_warning.metadata.json b/prowler/providers/googleworkspace/services/calendar/calendar_external_invitations_warning/calendar_external_invitations_warning.metadata.json index 4c7e71c102..3cca0005dd 100644 --- a/prowler/providers/googleworkspace/services/calendar/calendar_external_invitations_warning/calendar_external_invitations_warning.metadata.json +++ b/prowler/providers/googleworkspace/services/calendar/calendar_external_invitations_warning/calendar_external_invitations_warning.metadata.json @@ -13,9 +13,8 @@ "Risk": "Without external invitation warnings, users may unintentionally include **external guests** in internal meetings, exposing **confidential meeting details**, agendas, and internal attendee lists to unauthorized parties. This is a common vector for inadvertent data leakage through everyday calendar actions.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/6329284", - "https://knowledge.workspace.google.com/admin/calendar/set-google-calendar-sharing-options", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/calendar/allow-external-invitations-in-google-calendar-events", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/calendar/calendar_external_sharing_primary_calendar/calendar_external_sharing_primary_calendar.metadata.json b/prowler/providers/googleworkspace/services/calendar/calendar_external_sharing_primary_calendar/calendar_external_sharing_primary_calendar.metadata.json index 87a82946e5..d923754a9e 100644 --- a/prowler/providers/googleworkspace/services/calendar/calendar_external_sharing_primary_calendar/calendar_external_sharing_primary_calendar.metadata.json +++ b/prowler/providers/googleworkspace/services/calendar/calendar_external_sharing_primary_calendar/calendar_external_sharing_primary_calendar.metadata.json @@ -13,9 +13,8 @@ "Risk": "Overly permissive external sharing of primary calendars exposes **sensitive meeting metadata** — titles, attendees, locations, and descriptions — to users outside the organization. This increases the risk of **information disclosure**, **social engineering**, and **targeted phishing** based on insights into organizational activities.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/60765", "https://knowledge.workspace.google.com/admin/calendar/set-google-calendar-sharing-options", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/calendar/calendar_external_sharing_secondary_calendar/calendar_external_sharing_secondary_calendar.metadata.json b/prowler/providers/googleworkspace/services/calendar/calendar_external_sharing_secondary_calendar/calendar_external_sharing_secondary_calendar.metadata.json index bd2bbf5be7..d1ac914a0f 100644 --- a/prowler/providers/googleworkspace/services/calendar/calendar_external_sharing_secondary_calendar/calendar_external_sharing_secondary_calendar.metadata.json +++ b/prowler/providers/googleworkspace/services/calendar/calendar_external_sharing_secondary_calendar/calendar_external_sharing_secondary_calendar.metadata.json @@ -13,9 +13,8 @@ "Risk": "Overly permissive external sharing of secondary calendars exposes **project-specific or team-specific event details** to users outside the organization. Because secondary calendars often hold more targeted activities (e.g., product launches, internal reviews), unrestricted external sharing increases the risk of **information disclosure** and **competitive intelligence leakage**.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/60765", "https://knowledge.workspace.google.com/admin/calendar/set-google-calendar-sharing-options", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/chat/chat_apps_installation_disabled/chat_apps_installation_disabled.metadata.json b/prowler/providers/googleworkspace/services/chat/chat_apps_installation_disabled/chat_apps_installation_disabled.metadata.json index 54f4d6d852..070de032e1 100644 --- a/prowler/providers/googleworkspace/services/chat/chat_apps_installation_disabled/chat_apps_installation_disabled.metadata.json +++ b/prowler/providers/googleworkspace/services/chat/chat_apps_installation_disabled/chat_apps_installation_disabled.metadata.json @@ -13,8 +13,8 @@ "Risk": "Unrestricted Chat app installation allows **unvetted third-party applications** to access user data including conversation content and organizational information. An attacker could distribute a malicious Chat app to **exfiltrate confidential data** or establish **persistent access** to internal communications.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/6089179", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/apps/manage-the-marketplace-app-allowlist-for-your-organization", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/chat/chat_external_file_sharing_disabled/chat_external_file_sharing_disabled.metadata.json b/prowler/providers/googleworkspace/services/chat/chat_external_file_sharing_disabled/chat_external_file_sharing_disabled.metadata.json index c2d8323994..adc1b1fc5f 100644 --- a/prowler/providers/googleworkspace/services/chat/chat_external_file_sharing_disabled/chat_external_file_sharing_disabled.metadata.json +++ b/prowler/providers/googleworkspace/services/chat/chat_external_file_sharing_disabled/chat_external_file_sharing_disabled.metadata.json @@ -13,8 +13,8 @@ "Risk": "Enabled external file sharing allows users to send files containing **confidential information** to external parties through Chat. This creates a **data leakage** channel that bypasses DLP controls, particularly dangerous for organizations handling **regulated data** such as PII, PHI, or financial records.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/9540647", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/chat/set-up-chat-for-your-organization", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/chat/chat_external_messaging_restricted/chat_external_messaging_restricted.metadata.json b/prowler/providers/googleworkspace/services/chat/chat_external_messaging_restricted/chat_external_messaging_restricted.metadata.json index 0c366d7689..34b0016065 100644 --- a/prowler/providers/googleworkspace/services/chat/chat_external_messaging_restricted/chat_external_messaging_restricted.metadata.json +++ b/prowler/providers/googleworkspace/services/chat/chat_external_messaging_restricted/chat_external_messaging_restricted.metadata.json @@ -13,8 +13,8 @@ "Risk": "Unrestricted external messaging allows users to communicate freely with **any external party**, increasing the risk of **data exfiltration** through conversation content and **social engineering attacks** from untrusted domains targeting internal users.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/9540647", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/chat/set-up-chat-for-your-organization", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/chat/chat_external_spaces_restricted/chat_external_spaces_restricted.metadata.json b/prowler/providers/googleworkspace/services/chat/chat_external_spaces_restricted/chat_external_spaces_restricted.metadata.json index 9f40730d9f..1385f34ef8 100644 --- a/prowler/providers/googleworkspace/services/chat/chat_external_spaces_restricted/chat_external_spaces_restricted.metadata.json +++ b/prowler/providers/googleworkspace/services/chat/chat_external_spaces_restricted/chat_external_spaces_restricted.metadata.json @@ -13,8 +13,8 @@ "Risk": "Unrestricted external spaces allow users to add **anyone from any domain** to persistent group conversations. This increases the risk of **confidential information exposure** in shared spaces and enables **unauthorized external access** to ongoing organizational discussions.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/9540647", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/chat/set-up-chat-for-your-organization", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/chat/chat_incoming_webhooks_disabled/chat_incoming_webhooks_disabled.metadata.json b/prowler/providers/googleworkspace/services/chat/chat_incoming_webhooks_disabled/chat_incoming_webhooks_disabled.metadata.json index 7dec1c83e4..8e8f4b6301 100644 --- a/prowler/providers/googleworkspace/services/chat/chat_incoming_webhooks_disabled/chat_incoming_webhooks_disabled.metadata.json +++ b/prowler/providers/googleworkspace/services/chat/chat_incoming_webhooks_disabled/chat_incoming_webhooks_disabled.metadata.json @@ -13,8 +13,8 @@ "Risk": "Exposed webhook URLs allow **unauthorized content injection** into Chat spaces. Attackers can send **fraudulent or misleading messages** that appear to come from trusted services, creating a vector for **social engineering** and **phishing** within internal communications.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/6089179", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/apps/manage-the-marketplace-app-allowlist-for-your-organization", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/chat/chat_internal_file_sharing_disabled/chat_internal_file_sharing_disabled.metadata.json b/prowler/providers/googleworkspace/services/chat/chat_internal_file_sharing_disabled/chat_internal_file_sharing_disabled.metadata.json index af2130d741..b41f662cc3 100644 --- a/prowler/providers/googleworkspace/services/chat/chat_internal_file_sharing_disabled/chat_internal_file_sharing_disabled.metadata.json +++ b/prowler/providers/googleworkspace/services/chat/chat_internal_file_sharing_disabled/chat_internal_file_sharing_disabled.metadata.json @@ -13,8 +13,8 @@ "Risk": "Unrestricted internal file sharing in Chat allows files with **sensitive information** to be distributed freely without passing through approved channels. This undermines **data governance** and **audit trail** requirements, making it harder to track data movement within the organization.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/9540647", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/chat/set-up-chat-for-your-organization", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/directory/directory_super_admin_count/directory_super_admin_count.metadata.json b/prowler/providers/googleworkspace/services/directory/directory_super_admin_count/directory_super_admin_count.metadata.json index 24165aaade..fcd769cd12 100644 --- a/prowler/providers/googleworkspace/services/directory/directory_super_admin_count/directory_super_admin_count.metadata.json +++ b/prowler/providers/googleworkspace/services/directory/directory_super_admin_count/directory_super_admin_count.metadata.json @@ -14,7 +14,7 @@ "RelatedUrl": "", "AdditionalURLs": [ "https://knowledge.workspace.google.com/admin/users/prebuilt-administrator-roles", - "https://support.google.com/a/answer/9011373" + "https://knowledge.workspace.google.com/admin/users/security-best-practices-for-administrator-accounts" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/directory/directory_super_admin_only_admin_roles/directory_super_admin_only_admin_roles.metadata.json b/prowler/providers/googleworkspace/services/directory/directory_super_admin_only_admin_roles/directory_super_admin_only_admin_roles.metadata.json index 0264078982..ec531f10c8 100644 --- a/prowler/providers/googleworkspace/services/directory/directory_super_admin_only_admin_roles/directory_super_admin_only_admin_roles.metadata.json +++ b/prowler/providers/googleworkspace/services/directory/directory_super_admin_only_admin_roles/directory_super_admin_only_admin_roles.metadata.json @@ -14,7 +14,7 @@ "RelatedUrl": "", "AdditionalURLs": [ "https://knowledge.workspace.google.com/admin/users/prebuilt-administrator-roles", - "https://support.google.com/a/answer/9011373" + "https://knowledge.workspace.google.com/admin/users/security-best-practices-for-administrator-accounts" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/drive/drive_access_checker_recipients_only/drive_access_checker_recipients_only.metadata.json b/prowler/providers/googleworkspace/services/drive/drive_access_checker_recipients_only/drive_access_checker_recipients_only.metadata.json index a79283f9c3..8f1c14510a 100644 --- a/prowler/providers/googleworkspace/services/drive/drive_access_checker_recipients_only/drive_access_checker_recipients_only.metadata.json +++ b/prowler/providers/googleworkspace/services/drive/drive_access_checker_recipients_only/drive_access_checker_recipients_only.metadata.json @@ -13,8 +13,8 @@ "Risk": "If Access Checker suggests broader audiences or public visibility, users may **inadvertently widen access** to a file beyond the people they intended to share with. This is a common cause of unintentional internal or external over-sharing.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/60781", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/drive/manage-external-sharing-for-your-organization", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/drive/drive_desktop_access_disabled/drive_desktop_access_disabled.metadata.json b/prowler/providers/googleworkspace/services/drive/drive_desktop_access_disabled/drive_desktop_access_disabled.metadata.json index 43a08b0cab..576f78b19f 100644 --- a/prowler/providers/googleworkspace/services/drive/drive_desktop_access_disabled/drive_desktop_access_disabled.metadata.json +++ b/prowler/providers/googleworkspace/services/drive/drive_desktop_access_disabled/drive_desktop_access_disabled.metadata.json @@ -13,8 +13,8 @@ "Risk": "When Drive for desktop is enabled, organizational files are **synchronized to local devices** and remain accessible if the device is lost, stolen, or compromised. Because Drive for desktop bypasses the central offline-access controls, this channel is a frequently overlooked path for sensitive data to leave organization-managed environments.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/7491144", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/drive/set-up-drive-for-desktop-for-your-organization", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/drive/drive_external_sharing_warn_users/drive_external_sharing_warn_users.metadata.json b/prowler/providers/googleworkspace/services/drive/drive_external_sharing_warn_users/drive_external_sharing_warn_users.metadata.json index 8cfd0b4de8..474cadfe20 100644 --- a/prowler/providers/googleworkspace/services/drive/drive_external_sharing_warn_users/drive_external_sharing_warn_users.metadata.json +++ b/prowler/providers/googleworkspace/services/drive/drive_external_sharing_warn_users/drive_external_sharing_warn_users.metadata.json @@ -13,8 +13,8 @@ "Risk": "Without external sharing warnings, users may unintentionally share **sensitive documents** with external recipients who are not entitled to the data. This is a common vector for inadvertent leakage of intellectual property, personally identifiable information, and confidential business data through routine Drive sharing.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/60781", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/drive/manage-external-sharing-for-your-organization", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/drive/drive_internal_users_distribute_content/drive_internal_users_distribute_content.metadata.json b/prowler/providers/googleworkspace/services/drive/drive_internal_users_distribute_content/drive_internal_users_distribute_content.metadata.json index 9194d6d19e..df537051ba 100644 --- a/prowler/providers/googleworkspace/services/drive/drive_internal_users_distribute_content/drive_internal_users_distribute_content.metadata.json +++ b/prowler/providers/googleworkspace/services/drive/drive_internal_users_distribute_content/drive_internal_users_distribute_content.metadata.json @@ -13,8 +13,8 @@ "Risk": "If external users can move files from internal shared drives into shared drives owned by another organization, the organization **loses authoritative control** over its own data. This is a frequently overlooked path for unintentional or malicious data exfiltration through shared drive collaboration.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/60781", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/drive/manage-external-sharing-for-your-organization", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/drive/drive_publishing_files_disabled/drive_publishing_files_disabled.metadata.json b/prowler/providers/googleworkspace/services/drive/drive_publishing_files_disabled/drive_publishing_files_disabled.metadata.json index 9968665841..323d7f07a1 100644 --- a/prowler/providers/googleworkspace/services/drive/drive_publishing_files_disabled/drive_publishing_files_disabled.metadata.json +++ b/prowler/providers/googleworkspace/services/drive/drive_publishing_files_disabled/drive_publishing_files_disabled.metadata.json @@ -13,8 +13,8 @@ "Risk": "Allowing users to publish Drive files to the web creates a path for **unbounded data exposure**. Sensitive documents, intellectual property, customer data, or internal communications can be made publicly accessible — and indexed by search engines — with a single click, often unintentionally.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/60781", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/drive/manage-external-sharing-for-your-organization", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/drive/drive_shared_drive_creation_allowed/drive_shared_drive_creation_allowed.metadata.json b/prowler/providers/googleworkspace/services/drive/drive_shared_drive_creation_allowed/drive_shared_drive_creation_allowed.metadata.json index aba88e28b3..c27742b7ed 100644 --- a/prowler/providers/googleworkspace/services/drive/drive_shared_drive_creation_allowed/drive_shared_drive_creation_allowed.metadata.json +++ b/prowler/providers/googleworkspace/services/drive/drive_shared_drive_creation_allowed/drive_shared_drive_creation_allowed.metadata.json @@ -13,8 +13,8 @@ "Risk": "When users cannot create shared drives, they store collaborative content in their personal **My Drive** instead. When that user account is deleted, the data is also deleted, leading to **unintentional data loss** of organizationally significant information. Allowing shared drive creation makes data survivable across account lifecycle events.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/7212025", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://support.google.com/a/users/answer/7212025", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/drive/drive_shared_drive_disable_download_print_copy/drive_shared_drive_disable_download_print_copy.metadata.json b/prowler/providers/googleworkspace/services/drive/drive_shared_drive_disable_download_print_copy/drive_shared_drive_disable_download_print_copy.metadata.json index d4bf3300b7..b2a7738925 100644 --- a/prowler/providers/googleworkspace/services/drive/drive_shared_drive_disable_download_print_copy/drive_shared_drive_disable_download_print_copy.metadata.json +++ b/prowler/providers/googleworkspace/services/drive/drive_shared_drive_disable_download_print_copy/drive_shared_drive_disable_download_print_copy.metadata.json @@ -13,8 +13,8 @@ "Risk": "When viewers and commenters can download, print, or copy shared drive files, they can **bulk-extract sensitive content** — including intellectual property, personally identifiable information, and confidential business documents — using nothing more than read access. This is one of the most direct paths to data exfiltration through Drive.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/7662202", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/drive/manage-shared-drives-as-an-admin", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/drive/drive_shared_drive_managers_cannot_override/drive_shared_drive_managers_cannot_override.metadata.json b/prowler/providers/googleworkspace/services/drive/drive_shared_drive_managers_cannot_override/drive_shared_drive_managers_cannot_override.metadata.json index adc3555dc4..62e46c2ae2 100644 --- a/prowler/providers/googleworkspace/services/drive/drive_shared_drive_managers_cannot_override/drive_shared_drive_managers_cannot_override.metadata.json +++ b/prowler/providers/googleworkspace/services/drive/drive_shared_drive_managers_cannot_override/drive_shared_drive_managers_cannot_override.metadata.json @@ -13,8 +13,8 @@ "Risk": "If shared drive managers can override organizational defaults, **unauthorized data exposure** can occur when a manager intentionally or accidentally weakens a shared drive's security posture (for example, allowing external members or enabling download for viewers).", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/7662202", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/drive/manage-shared-drives-as-an-admin", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/drive/drive_shared_drive_members_only_access/drive_shared_drive_members_only_access.metadata.json b/prowler/providers/googleworkspace/services/drive/drive_shared_drive_members_only_access/drive_shared_drive_members_only_access.metadata.json index 0949a55040..d9495ea249 100644 --- a/prowler/providers/googleworkspace/services/drive/drive_shared_drive_members_only_access/drive_shared_drive_members_only_access.metadata.json +++ b/prowler/providers/googleworkspace/services/drive/drive_shared_drive_members_only_access/drive_shared_drive_members_only_access.metadata.json @@ -13,8 +13,8 @@ "Risk": "If non-members can be added to files inside a shared drive, the **drive's membership becomes meaningless** as a security control. Sensitive content scoped to a specific team can be silently extended to users who were never granted access to the drive itself, leading to unintended information disclosure.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/7662202", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/drive/manage-shared-drives-as-an-admin", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/drive/drive_sharing_allowlisted_domains/drive_sharing_allowlisted_domains.metadata.json b/prowler/providers/googleworkspace/services/drive/drive_sharing_allowlisted_domains/drive_sharing_allowlisted_domains.metadata.json index 46caa58bf7..ddba469b0b 100644 --- a/prowler/providers/googleworkspace/services/drive/drive_sharing_allowlisted_domains/drive_sharing_allowlisted_domains.metadata.json +++ b/prowler/providers/googleworkspace/services/drive/drive_sharing_allowlisted_domains/drive_sharing_allowlisted_domains.metadata.json @@ -13,8 +13,8 @@ "Risk": "When external sharing is unrestricted, users can share organizational content with **any external Google account**, including untrusted or unknown parties. Restricting sharing to allowlisted domains drastically reduces the surface area for accidental and malicious data exfiltration through Drive.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/60781", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/drive/manage-external-sharing-for-your-organization", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/drive/drive_warn_sharing_with_allowlisted_domains/drive_warn_sharing_with_allowlisted_domains.metadata.json b/prowler/providers/googleworkspace/services/drive/drive_warn_sharing_with_allowlisted_domains/drive_warn_sharing_with_allowlisted_domains.metadata.json index e41d074a23..c788df1a27 100644 --- a/prowler/providers/googleworkspace/services/drive/drive_warn_sharing_with_allowlisted_domains/drive_warn_sharing_with_allowlisted_domains.metadata.json +++ b/prowler/providers/googleworkspace/services/drive/drive_warn_sharing_with_allowlisted_domains/drive_warn_sharing_with_allowlisted_domains.metadata.json @@ -13,8 +13,8 @@ "Risk": "Allowlisted domains are still external. Users may not realize that even an allowlisted recipient is outside the organization, leading to **unintentional disclosure of sensitive content** to legitimate but external collaborators. A warning prompt at share time mitigates that without preventing the sharing itself.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/60781", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/drive/manage-external-sharing-for-your-organization", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/gmail/gmail_anomalous_attachment_protection_enabled/gmail_anomalous_attachment_protection_enabled.metadata.json b/prowler/providers/googleworkspace/services/gmail/gmail_anomalous_attachment_protection_enabled/gmail_anomalous_attachment_protection_enabled.metadata.json index 77a1c68b38..2295591dba 100644 --- a/prowler/providers/googleworkspace/services/gmail/gmail_anomalous_attachment_protection_enabled/gmail_anomalous_attachment_protection_enabled.metadata.json +++ b/prowler/providers/googleworkspace/services/gmail/gmail_anomalous_attachment_protection_enabled/gmail_anomalous_attachment_protection_enabled.metadata.json @@ -13,8 +13,8 @@ "Risk": "Without protection against anomalous attachment types, users may receive **emails with unusual file formats** that are designed to bypass standard security filters. Attackers may use **uncommon file extensions or MIME types** to deliver malware that evades signature-based detection.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/7676854", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/gmail/advanced/set-up-rules-to-detect-harmful-attachments", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/gmail/gmail_auto_forwarding_disabled/gmail_auto_forwarding_disabled.metadata.json b/prowler/providers/googleworkspace/services/gmail/gmail_auto_forwarding_disabled/gmail_auto_forwarding_disabled.metadata.json index 87d3111130..14484c251c 100644 --- a/prowler/providers/googleworkspace/services/gmail/gmail_auto_forwarding_disabled/gmail_auto_forwarding_disabled.metadata.json +++ b/prowler/providers/googleworkspace/services/gmail/gmail_auto_forwarding_disabled/gmail_auto_forwarding_disabled.metadata.json @@ -13,8 +13,8 @@ "Risk": "With auto-forwarding enabled, an attacker who gains control of a user account can create **forwarding rules to exfiltrate** all incoming email to an external address. This can persist undetected and provide the attacker with continuous access to sensitive communications even after the account is recovered.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/2491924", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/gmail/let-users-automatically-forward-their-own-gmail-emails", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/gmail/gmail_comprehensive_mail_storage_enabled/gmail_comprehensive_mail_storage_enabled.metadata.json b/prowler/providers/googleworkspace/services/gmail/gmail_comprehensive_mail_storage_enabled/gmail_comprehensive_mail_storage_enabled.metadata.json index 157a3da673..530d7af908 100644 --- a/prowler/providers/googleworkspace/services/gmail/gmail_comprehensive_mail_storage_enabled/gmail_comprehensive_mail_storage_enabled.metadata.json +++ b/prowler/providers/googleworkspace/services/gmail/gmail_comprehensive_mail_storage_enabled/gmail_comprehensive_mail_storage_enabled.metadata.json @@ -13,8 +13,8 @@ "Risk": "Without comprehensive mail storage, messages sent through other Google services (Calendar, Drive, etc.) may not be stored in Gmail and therefore **not subject to Vault retention policies**. This creates gaps in **compliance coverage**, **eDiscovery**, and **audit trails** that could violate regulatory requirements.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/3547347", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/gmail/advanced/set-up-comprehensive-mail-storage", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/gmail/gmail_domain_spoofing_protection_enabled/gmail_domain_spoofing_protection_enabled.metadata.json b/prowler/providers/googleworkspace/services/gmail/gmail_domain_spoofing_protection_enabled/gmail_domain_spoofing_protection_enabled.metadata.json index 4ae8bfb3c0..7e6be13d58 100644 --- a/prowler/providers/googleworkspace/services/gmail/gmail_domain_spoofing_protection_enabled/gmail_domain_spoofing_protection_enabled.metadata.json +++ b/prowler/providers/googleworkspace/services/gmail/gmail_domain_spoofing_protection_enabled/gmail_domain_spoofing_protection_enabled.metadata.json @@ -13,8 +13,8 @@ "Risk": "Without protection against domain spoofing based on similar domain names, users may receive **phishing emails from lookalike domains** (e.g., examp1e.com instead of example.com) that appear legitimate. This enables **credential theft, malware delivery, and business email compromise** attacks.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/9157861", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/gmail/advanced/advanced-phishing-and-malware-protection", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/gmail/gmail_employee_name_spoofing_protection_enabled/gmail_employee_name_spoofing_protection_enabled.metadata.json b/prowler/providers/googleworkspace/services/gmail/gmail_employee_name_spoofing_protection_enabled/gmail_employee_name_spoofing_protection_enabled.metadata.json index b72137019a..d6d107f360 100644 --- a/prowler/providers/googleworkspace/services/gmail/gmail_employee_name_spoofing_protection_enabled/gmail_employee_name_spoofing_protection_enabled.metadata.json +++ b/prowler/providers/googleworkspace/services/gmail/gmail_employee_name_spoofing_protection_enabled/gmail_employee_name_spoofing_protection_enabled.metadata.json @@ -13,8 +13,8 @@ "Risk": "Without protection against employee name spoofing, users may receive **emails that appear to come from colleagues or executives** but are actually from external attackers. This enables **business email compromise (BEC)**, **wire fraud**, and **social engineering attacks** that exploit trust relationships.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/9157861", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/gmail/advanced/advanced-phishing-and-malware-protection", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/gmail/gmail_encrypted_attachment_protection_enabled/gmail_encrypted_attachment_protection_enabled.metadata.json b/prowler/providers/googleworkspace/services/gmail/gmail_encrypted_attachment_protection_enabled/gmail_encrypted_attachment_protection_enabled.metadata.json index 459ce1f539..3367e11777 100644 --- a/prowler/providers/googleworkspace/services/gmail/gmail_encrypted_attachment_protection_enabled/gmail_encrypted_attachment_protection_enabled.metadata.json +++ b/prowler/providers/googleworkspace/services/gmail/gmail_encrypted_attachment_protection_enabled/gmail_encrypted_attachment_protection_enabled.metadata.json @@ -13,8 +13,8 @@ "Risk": "Without protection against encrypted attachments from untrusted senders, users may receive **password-protected archives containing malware** that bypass standard content scanning. Attackers commonly use encrypted attachments to evade detection and deliver **ransomware, trojans, or other malicious payloads**.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/7676854", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/gmail/advanced/set-up-rules-to-detect-harmful-attachments", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/gmail/gmail_enhanced_pre_delivery_scanning_enabled/gmail_enhanced_pre_delivery_scanning_enabled.metadata.json b/prowler/providers/googleworkspace/services/gmail/gmail_enhanced_pre_delivery_scanning_enabled/gmail_enhanced_pre_delivery_scanning_enabled.metadata.json index 69b1be1e4b..0f14ecd73b 100644 --- a/prowler/providers/googleworkspace/services/gmail/gmail_enhanced_pre_delivery_scanning_enabled/gmail_enhanced_pre_delivery_scanning_enabled.metadata.json +++ b/prowler/providers/googleworkspace/services/gmail/gmail_enhanced_pre_delivery_scanning_enabled/gmail_enhanced_pre_delivery_scanning_enabled.metadata.json @@ -13,8 +13,8 @@ "Risk": "Without enhanced pre-delivery scanning, some **sophisticated phishing and malware** messages may pass through standard filters and be delivered to users. The additional scanning layer catches threats that the first-pass filters miss, reducing the organization's exposure to **zero-day phishing campaigns** and **targeted attacks**.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/7380368", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/security/help-prevent-phishing-with-pre-delivery-message-scanning", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/gmail/gmail_external_image_scanning_enabled/gmail_external_image_scanning_enabled.metadata.json b/prowler/providers/googleworkspace/services/gmail/gmail_external_image_scanning_enabled/gmail_external_image_scanning_enabled.metadata.json index d6a087183c..4df5f8bd84 100644 --- a/prowler/providers/googleworkspace/services/gmail/gmail_external_image_scanning_enabled/gmail_external_image_scanning_enabled.metadata.json +++ b/prowler/providers/googleworkspace/services/gmail/gmail_external_image_scanning_enabled/gmail_external_image_scanning_enabled.metadata.json @@ -13,8 +13,8 @@ "Risk": "Without external image scanning, attackers can use **linked images to track email opens**, deliver **exploit payloads via image rendering vulnerabilities**, or use images as part of sophisticated **phishing schemes** that mimic legitimate communications.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/7676854", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/gmail/advanced/advanced-phishing-and-malware-protection", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/gmail/gmail_groups_spoofing_protection_enabled/gmail_groups_spoofing_protection_enabled.metadata.json b/prowler/providers/googleworkspace/services/gmail/gmail_groups_spoofing_protection_enabled/gmail_groups_spoofing_protection_enabled.metadata.json index 0c3dd64761..c5f5ee61a9 100644 --- a/prowler/providers/googleworkspace/services/gmail/gmail_groups_spoofing_protection_enabled/gmail_groups_spoofing_protection_enabled.metadata.json +++ b/prowler/providers/googleworkspace/services/gmail/gmail_groups_spoofing_protection_enabled/gmail_groups_spoofing_protection_enabled.metadata.json @@ -13,8 +13,8 @@ "Risk": "Without protection of groups from domain-spoofing emails, attackers can send **spoofed messages to group mailboxes** that appear to originate from the organization. Since groups distribute to many recipients, a single spoofed email can enable **mass phishing, social engineering, or misinformation** campaigns across the organization.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/9157861", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/gmail/advanced/advanced-phishing-and-malware-protection", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/gmail/gmail_inbound_domain_spoofing_protection_enabled/gmail_inbound_domain_spoofing_protection_enabled.metadata.json b/prowler/providers/googleworkspace/services/gmail/gmail_inbound_domain_spoofing_protection_enabled/gmail_inbound_domain_spoofing_protection_enabled.metadata.json index 9e285621c8..a049a7ede5 100644 --- a/prowler/providers/googleworkspace/services/gmail/gmail_inbound_domain_spoofing_protection_enabled/gmail_inbound_domain_spoofing_protection_enabled.metadata.json +++ b/prowler/providers/googleworkspace/services/gmail/gmail_inbound_domain_spoofing_protection_enabled/gmail_inbound_domain_spoofing_protection_enabled.metadata.json @@ -13,8 +13,8 @@ "Risk": "Without protection against inbound domain spoofing, users may receive **emails that appear to come from their own organization** but are sent by external attackers. This enables **internal impersonation**, **phishing**, and **business email compromise** attacks that exploit trust in internal communications.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/9157861", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/gmail/advanced/advanced-phishing-and-malware-protection", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/gmail/gmail_mail_delegation_disabled/gmail_mail_delegation_disabled.metadata.json b/prowler/providers/googleworkspace/services/gmail/gmail_mail_delegation_disabled/gmail_mail_delegation_disabled.metadata.json index a23cf60741..a4f9fd0460 100644 --- a/prowler/providers/googleworkspace/services/gmail/gmail_mail_delegation_disabled/gmail_mail_delegation_disabled.metadata.json +++ b/prowler/providers/googleworkspace/services/gmail/gmail_mail_delegation_disabled/gmail_mail_delegation_disabled.metadata.json @@ -13,8 +13,8 @@ "Risk": "If users can delegate access to their mailbox, an attacker who compromises one account could silently delegate access to maintain persistent email surveillance. This also increases the risk of **insider threats** and **data exfiltration** through shared mailbox access.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/7223765", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/gmail/let-users-delegate-access-to-a-gmail-account", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/gmail/gmail_per_user_outbound_gateway_disabled/gmail_per_user_outbound_gateway_disabled.metadata.json b/prowler/providers/googleworkspace/services/gmail/gmail_per_user_outbound_gateway_disabled/gmail_per_user_outbound_gateway_disabled.metadata.json index 2f68b3adbc..95cd7567c3 100644 --- a/prowler/providers/googleworkspace/services/gmail/gmail_per_user_outbound_gateway_disabled/gmail_per_user_outbound_gateway_disabled.metadata.json +++ b/prowler/providers/googleworkspace/services/gmail/gmail_per_user_outbound_gateway_disabled/gmail_per_user_outbound_gateway_disabled.metadata.json @@ -13,8 +13,8 @@ "Risk": "With per-user outbound gateways enabled, users can route outbound email through **external SMTP servers**, bypassing organizational **email security controls**, **DLP policies**, and **audit logging**. This creates an unmonitored channel for data exfiltration and policy circumvention.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/176652", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/gmail/advanced/allow-per-user-outbound-gateways", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/gmail/gmail_pop_imap_access_disabled/gmail_pop_imap_access_disabled.metadata.json b/prowler/providers/googleworkspace/services/gmail/gmail_pop_imap_access_disabled/gmail_pop_imap_access_disabled.metadata.json index 76affe3e47..48f077d8ea 100644 --- a/prowler/providers/googleworkspace/services/gmail/gmail_pop_imap_access_disabled/gmail_pop_imap_access_disabled.metadata.json +++ b/prowler/providers/googleworkspace/services/gmail/gmail_pop_imap_access_disabled/gmail_pop_imap_access_disabled.metadata.json @@ -13,8 +13,8 @@ "Risk": "With POP and IMAP enabled, users can access email through **legacy clients** that rely on simple password authentication, bypassing **multifactor authentication** and other modern security controls. This significantly increases the risk of **credential-based account compromise**.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/105694", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/sync/turn-pop-and-imap-on-or-off-for-users", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/gmail/gmail_script_attachment_protection_enabled/gmail_script_attachment_protection_enabled.metadata.json b/prowler/providers/googleworkspace/services/gmail/gmail_script_attachment_protection_enabled/gmail_script_attachment_protection_enabled.metadata.json index fc2a3af1b0..cae7d474ac 100644 --- a/prowler/providers/googleworkspace/services/gmail/gmail_script_attachment_protection_enabled/gmail_script_attachment_protection_enabled.metadata.json +++ b/prowler/providers/googleworkspace/services/gmail/gmail_script_attachment_protection_enabled/gmail_script_attachment_protection_enabled.metadata.json @@ -13,8 +13,8 @@ "Risk": "Without protection against script-bearing attachments from untrusted senders, users may receive **files containing malicious scripts** that can execute harmful code when opened. Attackers commonly use script attachments to deliver **malware, backdoors, or credential stealers**.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/7676854", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/gmail/advanced/set-up-rules-to-detect-harmful-attachments", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/gmail/gmail_shortener_scanning_enabled/gmail_shortener_scanning_enabled.metadata.json b/prowler/providers/googleworkspace/services/gmail/gmail_shortener_scanning_enabled/gmail_shortener_scanning_enabled.metadata.json index 4ea88e92d3..518b90b962 100644 --- a/prowler/providers/googleworkspace/services/gmail/gmail_shortener_scanning_enabled/gmail_shortener_scanning_enabled.metadata.json +++ b/prowler/providers/googleworkspace/services/gmail/gmail_shortener_scanning_enabled/gmail_shortener_scanning_enabled.metadata.json @@ -13,8 +13,8 @@ "Risk": "Without shortened URL scanning, attackers can use **URL shortening services** to hide malicious destinations in phishing emails. Users cannot visually verify where the link leads, increasing the success rate of **phishing and credential harvesting** attacks.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/7676854", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/gmail/advanced/advanced-phishing-and-malware-protection", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/gmail/gmail_unauthenticated_email_protection_enabled/gmail_unauthenticated_email_protection_enabled.metadata.json b/prowler/providers/googleworkspace/services/gmail/gmail_unauthenticated_email_protection_enabled/gmail_unauthenticated_email_protection_enabled.metadata.json index 6199080224..ec730d0a8b 100644 --- a/prowler/providers/googleworkspace/services/gmail/gmail_unauthenticated_email_protection_enabled/gmail_unauthenticated_email_protection_enabled.metadata.json +++ b/prowler/providers/googleworkspace/services/gmail/gmail_unauthenticated_email_protection_enabled/gmail_unauthenticated_email_protection_enabled.metadata.json @@ -13,8 +13,8 @@ "Risk": "Without protection against unauthenticated emails, users may receive **spoofed or forged messages** that fail SPF and DKIM checks but are still delivered normally. This enables **phishing**, **spam**, and **impersonation attacks** that exploit the lack of sender verification.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/9157861", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/gmail/advanced/advanced-phishing-and-malware-protection", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/gmail/gmail_untrusted_link_warnings_enabled/gmail_untrusted_link_warnings_enabled.metadata.json b/prowler/providers/googleworkspace/services/gmail/gmail_untrusted_link_warnings_enabled/gmail_untrusted_link_warnings_enabled.metadata.json index eb350e3b0f..78678ff9f5 100644 --- a/prowler/providers/googleworkspace/services/gmail/gmail_untrusted_link_warnings_enabled/gmail_untrusted_link_warnings_enabled.metadata.json +++ b/prowler/providers/googleworkspace/services/gmail/gmail_untrusted_link_warnings_enabled/gmail_untrusted_link_warnings_enabled.metadata.json @@ -13,8 +13,8 @@ "Risk": "Without untrusted link warnings, users may click on **phishing links** or links to **malware distribution sites** without any warning. This significantly increases the success rate of **social engineering attacks** targeting the organization.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/7676854", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/gmail/advanced/advanced-phishing-and-malware-protection", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/groups/groups_creation_restricted/groups_creation_restricted.metadata.json b/prowler/providers/googleworkspace/services/groups/groups_creation_restricted/groups_creation_restricted.metadata.json index c2725de695..6a5bb80c3e 100644 --- a/prowler/providers/googleworkspace/services/groups/groups_creation_restricted/groups_creation_restricted.metadata.json +++ b/prowler/providers/googleworkspace/services/groups/groups_creation_restricted/groups_creation_restricted.metadata.json @@ -13,8 +13,8 @@ "Risk": "Allowing any user to create groups with external members or incoming email from outside increases the risk of **unauthorized data sharing**, **spam delivery**, and **shadow IT** groups that bypass organizational controls.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/10308022", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/groups/what-you-get-with-groups-for-business", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/groups/groups_external_access_restricted/groups_external_access_restricted.metadata.json b/prowler/providers/googleworkspace/services/groups/groups_external_access_restricted/groups_external_access_restricted.metadata.json index 3656879182..9d32063c90 100644 --- a/prowler/providers/googleworkspace/services/groups/groups_external_access_restricted/groups_external_access_restricted.metadata.json +++ b/prowler/providers/googleworkspace/services/groups/groups_external_access_restricted/groups_external_access_restricted.metadata.json @@ -13,8 +13,8 @@ "Risk": "Allowing external access to groups exposes **group names, descriptions, and membership** to anyone outside the organization, increasing the risk of **information disclosure** and enabling external parties to identify targets for **social engineering attacks**.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/10308022", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/groups/what-you-get-with-groups-for-business", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/groups/groups_view_conversations_restricted/groups_view_conversations_restricted.metadata.json b/prowler/providers/googleworkspace/services/groups/groups_view_conversations_restricted/groups_view_conversations_restricted.metadata.json index b90fe6041b..88f0927155 100644 --- a/prowler/providers/googleworkspace/services/groups/groups_view_conversations_restricted/groups_view_conversations_restricted.metadata.json +++ b/prowler/providers/googleworkspace/services/groups/groups_view_conversations_restricted/groups_view_conversations_restricted.metadata.json @@ -13,8 +13,8 @@ "Risk": "Allowing all organization users or anyone to view group conversations can lead to **information disclosure** of sensitive discussions, internal decisions, and confidential data shared within groups.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/10308022", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/groups/what-you-get-with-groups-for-business", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/marketplace/marketplace_apps_access_restricted/marketplace_apps_access_restricted.metadata.json b/prowler/providers/googleworkspace/services/marketplace/marketplace_apps_access_restricted/marketplace_apps_access_restricted.metadata.json index 7d982370a1..78fe1850ad 100644 --- a/prowler/providers/googleworkspace/services/marketplace/marketplace_apps_access_restricted/marketplace_apps_access_restricted.metadata.json +++ b/prowler/providers/googleworkspace/services/marketplace/marketplace_apps_access_restricted/marketplace_apps_access_restricted.metadata.json @@ -13,8 +13,8 @@ "Risk": "Allowing unrestricted Marketplace app installation exposes the organization to **unvetted third-party applications** that may request broad OAuth scopes, potentially gaining access to **sensitive organizational data** including emails, documents, and calendar events without proper security review.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/6089179", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/apps/manage-the-marketplace-app-allowlist-for-your-organization", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/security/security_2sv_enforced/security_2sv_enforced.metadata.json b/prowler/providers/googleworkspace/services/security/security_2sv_enforced/security_2sv_enforced.metadata.json index 659360aac5..3a2c0b4566 100644 --- a/prowler/providers/googleworkspace/services/security/security_2sv_enforced/security_2sv_enforced.metadata.json +++ b/prowler/providers/googleworkspace/services/security/security_2sv_enforced/security_2sv_enforced.metadata.json @@ -14,7 +14,7 @@ "RelatedUrl": "", "AdditionalURLs": [ "https://knowledge.workspace.google.com/admin/security/protect-your-business-with-2-step-verification", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/security/security_2sv_hardware_keys_admins/security_2sv_hardware_keys_admins.metadata.json b/prowler/providers/googleworkspace/services/security/security_2sv_hardware_keys_admins/security_2sv_hardware_keys_admins.metadata.json index 586cfc766a..4aae5840a5 100644 --- a/prowler/providers/googleworkspace/services/security/security_2sv_hardware_keys_admins/security_2sv_hardware_keys_admins.metadata.json +++ b/prowler/providers/googleworkspace/services/security/security_2sv_hardware_keys_admins/security_2sv_hardware_keys_admins.metadata.json @@ -14,7 +14,7 @@ "RelatedUrl": "", "AdditionalURLs": [ "https://knowledge.workspace.google.com/admin/security/protect-your-business-with-2-step-verification", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/security/security_advanced_protection_configured/security_advanced_protection_configured.metadata.json b/prowler/providers/googleworkspace/services/security/security_advanced_protection_configured/security_advanced_protection_configured.metadata.json index 6e9d72bfdf..10c5d9e788 100644 --- a/prowler/providers/googleworkspace/services/security/security_advanced_protection_configured/security_advanced_protection_configured.metadata.json +++ b/prowler/providers/googleworkspace/services/security/security_advanced_protection_configured/security_advanced_protection_configured.metadata.json @@ -14,7 +14,7 @@ "RelatedUrl": "", "AdditionalURLs": [ "https://knowledge.workspace.google.com/admin/security/protect-users-with-the-advanced-protection-program", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/security/security_app_access_restricted/security_app_access_restricted.metadata.json b/prowler/providers/googleworkspace/services/security/security_app_access_restricted/security_app_access_restricted.metadata.json index 9a165141b6..0945fccdec 100644 --- a/prowler/providers/googleworkspace/services/security/security_app_access_restricted/security_app_access_restricted.metadata.json +++ b/prowler/providers/googleworkspace/services/security/security_app_access_restricted/security_app_access_restricted.metadata.json @@ -14,7 +14,7 @@ "RelatedUrl": "", "AdditionalURLs": [ "https://knowledge.workspace.google.com/admin/apps/control-which-apps-access-google-workspace-data", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/security/security_dlp_drive_rules_configured/security_dlp_drive_rules_configured.metadata.json b/prowler/providers/googleworkspace/services/security/security_dlp_drive_rules_configured/security_dlp_drive_rules_configured.metadata.json index e25e45c395..e19c3292b8 100644 --- a/prowler/providers/googleworkspace/services/security/security_dlp_drive_rules_configured/security_dlp_drive_rules_configured.metadata.json +++ b/prowler/providers/googleworkspace/services/security/security_dlp_drive_rules_configured/security_dlp_drive_rules_configured.metadata.json @@ -14,7 +14,7 @@ "RelatedUrl": "", "AdditionalURLs": [ "https://knowledge.workspace.google.com/admin/security/about-dlp", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/security/security_internal_apps_trusted/security_internal_apps_trusted.metadata.json b/prowler/providers/googleworkspace/services/security/security_internal_apps_trusted/security_internal_apps_trusted.metadata.json index abf11509f4..e4b055f6e1 100644 --- a/prowler/providers/googleworkspace/services/security/security_internal_apps_trusted/security_internal_apps_trusted.metadata.json +++ b/prowler/providers/googleworkspace/services/security/security_internal_apps_trusted/security_internal_apps_trusted.metadata.json @@ -14,7 +14,7 @@ "RelatedUrl": "", "AdditionalURLs": [ "https://knowledge.workspace.google.com/admin/apps/control-which-apps-access-google-workspace-data", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/security/security_less_secure_apps_disabled/security_less_secure_apps_disabled.metadata.json b/prowler/providers/googleworkspace/services/security/security_less_secure_apps_disabled/security_less_secure_apps_disabled.metadata.json index ef4531b758..c6c2ff57e9 100644 --- a/prowler/providers/googleworkspace/services/security/security_less_secure_apps_disabled/security_less_secure_apps_disabled.metadata.json +++ b/prowler/providers/googleworkspace/services/security/security_less_secure_apps_disabled/security_less_secure_apps_disabled.metadata.json @@ -14,7 +14,7 @@ "RelatedUrl": "", "AdditionalURLs": [ "https://knowledge.workspace.google.com/admin/apps/control-access-to-less-secure-apps", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/security/security_login_challenges_configured/security_login_challenges_configured.metadata.json b/prowler/providers/googleworkspace/services/security/security_login_challenges_configured/security_login_challenges_configured.metadata.json index 0f4433ca78..ec945474fc 100644 --- a/prowler/providers/googleworkspace/services/security/security_login_challenges_configured/security_login_challenges_configured.metadata.json +++ b/prowler/providers/googleworkspace/services/security/security_login_challenges_configured/security_login_challenges_configured.metadata.json @@ -14,7 +14,7 @@ "RelatedUrl": "", "AdditionalURLs": [ "https://knowledge.workspace.google.com/admin/security/protect-google-workspace-accounts-with-security-challenges", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/security/security_password_policy_strong/security_password_policy_strong.metadata.json b/prowler/providers/googleworkspace/services/security/security_password_policy_strong/security_password_policy_strong.metadata.json index 2bd351e93b..776c9745e8 100644 --- a/prowler/providers/googleworkspace/services/security/security_password_policy_strong/security_password_policy_strong.metadata.json +++ b/prowler/providers/googleworkspace/services/security/security_password_policy_strong/security_password_policy_strong.metadata.json @@ -14,7 +14,7 @@ "RelatedUrl": "", "AdditionalURLs": [ "https://knowledge.workspace.google.com/admin/users/enforce-and-monitor-password-requirements-for-users", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/security/security_session_duration_limited/security_session_duration_limited.metadata.json b/prowler/providers/googleworkspace/services/security/security_session_duration_limited/security_session_duration_limited.metadata.json index 5cc4916945..83e89527d8 100644 --- a/prowler/providers/googleworkspace/services/security/security_session_duration_limited/security_session_duration_limited.metadata.json +++ b/prowler/providers/googleworkspace/services/security/security_session_duration_limited/security_session_duration_limited.metadata.json @@ -14,7 +14,7 @@ "RelatedUrl": "", "AdditionalURLs": [ "https://knowledge.workspace.google.com/admin/security/set-session-length-for-google-services", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/security/security_super_admin_recovery_disabled/security_super_admin_recovery_disabled.metadata.json b/prowler/providers/googleworkspace/services/security/security_super_admin_recovery_disabled/security_super_admin_recovery_disabled.metadata.json index c39fb69d03..540faddac9 100644 --- a/prowler/providers/googleworkspace/services/security/security_super_admin_recovery_disabled/security_super_admin_recovery_disabled.metadata.json +++ b/prowler/providers/googleworkspace/services/security/security_super_admin_recovery_disabled/security_super_admin_recovery_disabled.metadata.json @@ -14,7 +14,7 @@ "RelatedUrl": "", "AdditionalURLs": [ "https://knowledge.workspace.google.com/admin/users/allow-super-administrators-to-recover-their-password", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/security/security_user_recovery_enabled/security_user_recovery_enabled.metadata.json b/prowler/providers/googleworkspace/services/security/security_user_recovery_enabled/security_user_recovery_enabled.metadata.json index 4d3e7dcb64..0a2de745db 100644 --- a/prowler/providers/googleworkspace/services/security/security_user_recovery_enabled/security_user_recovery_enabled.metadata.json +++ b/prowler/providers/googleworkspace/services/security/security_user_recovery_enabled/security_user_recovery_enabled.metadata.json @@ -14,7 +14,7 @@ "RelatedUrl": "", "AdditionalURLs": [ "https://knowledge.workspace.google.com/admin/users/set-up-password-recovery-for-users", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/prowler/providers/googleworkspace/services/sites/sites_service_disabled/sites_service_disabled.metadata.json b/prowler/providers/googleworkspace/services/sites/sites_service_disabled/sites_service_disabled.metadata.json index 6422fa974b..3871b358a2 100644 --- a/prowler/providers/googleworkspace/services/sites/sites_service_disabled/sites_service_disabled.metadata.json +++ b/prowler/providers/googleworkspace/services/sites/sites_service_disabled/sites_service_disabled.metadata.json @@ -13,8 +13,8 @@ "Risk": "When Google Sites is enabled, users can create websites that may **inadvertently expose internal information** to external parties. These sites can be difficult to track and manage, creating potential **data leakage vectors** outside the organization's standard content management controls.", "RelatedUrl": "", "AdditionalURLs": [ - "https://support.google.com/a/answer/182442", - "https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings" + "https://knowledge.workspace.google.com/admin/users/advanced/turn-a-service-on-or-off-for-google-workspace-users", + "https://docs.cloud.google.com/identity/docs/concepts/supported-policy-api-settings" ], "Remediation": { "Code": { diff --git a/pyproject.toml b/pyproject.toml index 61f0ee5fc9..a87e200e4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -123,7 +123,7 @@ maintainers = [{name = "Prowler Engineering", email = "engineering@prowler.com"} name = "prowler" readme = "README.md" requires-python = ">=3.10,<3.13" -version = "5.29.0" +version = "5.30.0" [project.scripts] prowler = "prowler.__main__:prowler" 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/lib/check/tool_wrapper_test.py b/tests/lib/check/tool_wrapper_test.py index ed3b897d8d..5f4f0c7f88 100644 --- a/tests/lib/check/tool_wrapper_test.py +++ b/tests/lib/check/tool_wrapper_test.py @@ -70,6 +70,20 @@ class TestIsToolWrapperProvider: assert is_tool_wrapper_provider("does-not-exist") is False + @patch("prowler.lib.check.tool_wrapper.importlib.metadata.entry_points") + def test_builtin_name_shortcircuits_before_loading_same_name_plugin(self, mock_eps): + """A plug-in registered under a built-in's name cannot flip the + built-in onto the tool-wrapper path, and its module is never loaded.""" + from prowler.lib.check.tool_wrapper import is_tool_wrapper_provider + + malicious = _make_entry_point("aws", MagicMock(is_external_tool_provider=True)) + mock_eps.return_value = [malicious] + + # `aws` is a built-in, so classification short-circuits to False... + assert is_tool_wrapper_provider("aws") is False + # ...and the shadowing plug-in's code is never executed via ep.load(). + malicious.load.assert_not_called() + class TestLoadEpClass: """_load_ep_class: cache, broken plug-ins, no-match.""" diff --git a/tests/providers/external/test_dynamic_provider_loading.py b/tests/providers/external/test_dynamic_provider_loading.py index fd91f8b005..aff56c7246 100644 --- a/tests/providers/external/test_dynamic_provider_loading.py +++ b/tests/providers/external/test_dynamic_provider_loading.py @@ -663,22 +663,25 @@ class TestProviderInitialization: @patch("prowler.providers.common.provider.logger") @patch("prowler.providers.common.provider.load_and_validate_config_file") - @patch("prowler.providers.common.provider.Provider._load_ep_provider") + @patch("prowler.providers.common.provider.importlib.metadata.entry_points") @patch("prowler.providers.common.provider.import_module") @patch("prowler.providers.common.provider.Provider.is_builtin") def test_init_global_provider_warns_when_plugin_shadowed_by_builtin( - self, mock_is_builtin, mock_import, mock_load_ep, mock_config, mock_logger + self, mock_is_builtin, mock_import, mock_entry_points, mock_config, mock_logger ): """Regression guard: when a plug-in registers a provider name that collides with a built-in, the BUILT-IN wins and a warning is emitted - naming the shadowed plug-in. Matches the precedence enforced by - `_resolve_check_module` and `CheckMetadata.get_bulk` for checks. See - PR #10700 review (HugoPBrito). + naming the shadowed plug-in. Shadow detection matches by entry-point + name only — the plug-in is never `ep.load()`-ed just to warn, so its + module code cannot run during a built-in run. See PR #10700 review + (HugoPBrito, Alan-TheGentleman). """ # Simulate a built-in `aws` that exists, AND a plug-in registered # under the same `aws` name via entry points. mock_is_builtin.return_value = True - mock_load_ep.return_value = FakeExternalProvider # plug-in shadow + shadow_ep = MagicMock() + shadow_ep.name = "aws" # plug-in shadowing the built-in name + mock_entry_points.return_value = [shadow_ep] mock_import.return_value = MagicMock( AwsProvider=MagicMock(side_effect=lambda **_kw: None) ) @@ -723,6 +726,8 @@ class TestProviderInitialization: ] assert warning_msgs, "expected a warning about the shadowed plug-in 'aws'" assert "IGNORED" in warning_msgs[0] + # Shadow detected by name only — plug-in code never executed to warn + shadow_ep.load.assert_not_called() # =========================================================================== @@ -1354,6 +1359,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 @@ -1969,12 +2031,11 @@ class TestGetClass: mock_is_builtin.return_value = False mock_ep.return_value = [] - with pytest.raises((ImportError, Exception)) as exc_info: - Provider.get_class("totally_unknown_xyz_provider") - - # Must NOT be a SystemExit — that belongs in init_global_provider's + # Assert ImportError specifically to enforce the public API contract + # (not a broad Exception). SystemExit belongs in init_global_provider's # wrapper, not in the pure resolver. - assert not isinstance(exc_info.value, SystemExit) + with pytest.raises(ImportError): + Provider.get_class("totally_unknown_xyz_provider") # ----------------------------------------------------------------------- # T4: get_class is PURE for built-ins — no collision warning, no EP call @@ -2104,11 +2165,11 @@ class TestGetClass: # ----------------------------------------------------------------------- @patch("prowler.providers.common.provider.load_and_validate_config_file") - @patch("prowler.providers.common.provider.Provider._load_ep_provider") + @patch("prowler.providers.common.provider.importlib.metadata.entry_points") @patch("prowler.providers.common.provider.import_module") @patch("prowler.providers.common.provider.Provider.is_builtin") def test_init_global_provider_emits_collision_warning_for_builtin_ep_shadow( - self, mock_is_builtin, mock_import, mock_load_ep, mock_config, caplog + self, mock_is_builtin, mock_import, mock_entry_points, mock_config, caplog ): """init_global_provider (not get_class) emits the collision warning when a built-in provider has a same-named entry-point plug-in registered. @@ -2117,13 +2178,16 @@ class TestGetClass: the warning responsibility moved OUT of get_class and INTO init_global_provider, so users still see the message on CLI invocation but prowler --help and API calls (which never hit init_global_provider) - do not spuriously emit it. + do not spuriously emit it. The shadow is detected by entry-point name + only — the plug-in is never loaded to warn. """ import logging import types mock_is_builtin.return_value = True - mock_load_ep.return_value = FakeExternalProvider # plug-in shadow + shadow_ep = MagicMock() + shadow_ep.name = "aws" # plug-in shadowing the built-in name + mock_entry_points.return_value = [shadow_ep] fake_module = types.ModuleType("fake_builtin_module") fake_module.AwsProvider = MagicMock(side_effect=lambda **_kw: None) @@ -2169,3 +2233,5 @@ class TestGetClass: "init_global_provider must emit the collision warning when a " "same-named EP plug-in exists for a built-in provider" ) + # Shadow detected by name only — the plug-in is never loaded to warn. + shadow_ep.load.assert_not_called() diff --git a/ui/app/(prowler)/providers/page.test.ts b/ui/app/(prowler)/providers/page.test.ts index 52e22c9ba8..96612380df 100644 --- a/ui/app/(prowler)/providers/page.test.ts +++ b/ui/app/(prowler)/providers/page.test.ts @@ -34,4 +34,13 @@ describe("providers page", () => { expect(source).toContain("size: 160"); expect(source).toContain("size: 140"); }); + + it("keeps the CLI import banner gated by the Cloud environment", () => { + const currentDir = path.dirname(fileURLToPath(import.meta.url)); + const pagePath = path.join(currentDir, "page.tsx"); + const source = readFileSync(pagePath, "utf8"); + + expect(source).toContain("NEXT_PUBLIC_IS_CLOUD_ENV"); + expect(source).toContain("{isCloudEnvironment && + {isCloudEnvironment && } { title: "Scan Launched", }), ); + const toastPayload = toastMock.mock.calls[0]?.[0]; + expect(toastPayload.action.props.children.props.href).toBe( + `/scans?tab=${SCAN_JOBS_TAB.ACTIVE}`, + ); }); }); diff --git a/ui/components/providers/wizard/steps/launch-step.tsx b/ui/components/providers/wizard/steps/launch-step.tsx index bbefb717d8..1e63f6700d 100644 --- a/ui/components/providers/wizard/steps/launch-step.tsx +++ b/ui/components/providers/wizard/steps/launch-step.tsx @@ -15,6 +15,7 @@ import { Spinner } from "@/components/shadcn/spinner/spinner"; import { TreeStatusIcon } from "@/components/shadcn/tree-view/tree-status-icon"; import { ToastAction, useToast } from "@/components/ui"; import { useProviderWizardStore } from "@/store/provider-wizard/store"; +import { SCAN_JOBS_TAB } from "@/types"; import { TREE_ITEM_STATUS } from "@/types/tree"; import { @@ -81,7 +82,7 @@ export function LaunchStep({ : "Single scan launched successfully.", action: ( - Go to scans + Go to scans ), }); diff --git a/ui/components/scans/cli-import-banner.test.tsx b/ui/components/scans/cli-import-banner.test.tsx new file mode 100644 index 0000000000..f324b3ab02 --- /dev/null +++ b/ui/components/scans/cli-import-banner.test.tsx @@ -0,0 +1,89 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { DOCS_URLS } from "@/lib/external-urls"; + +import { CliImportBanner } from "./cli-import-banner"; + +const STORAGE_KEY = "prowler:cli-import-banner-dismissed"; + +const localStorageMock = (() => { + let store: Record = {}; + + return { + getItem: vi.fn((key: string) => store[key] ?? null), + setItem: vi.fn((key: string, value: string) => { + store[key] = value; + }), + removeItem: vi.fn((key: string) => { + delete store[key]; + }), + clear: vi.fn(() => { + store = {}; + }), + get length() { + return Object.keys(store).length; + }, + key: vi.fn((index: number) => Object.keys(store)[index] ?? null), + }; +})(); + +Object.defineProperty(window, "localStorage", { + value: localStorageMock, + writable: true, +}); + +describe("CliImportBanner", () => { + beforeEach(() => { + localStorageMock.clear(); + vi.clearAllMocks(); + }); + + it("renders the banner when not dismissed", () => { + render(); + + expect( + screen.getByText(/Import findings from Prowler CLI/), + ).toBeInTheDocument(); + }); + + it("renders a link to the documentation", () => { + render(); + + const link = screen.getByRole("link", { name: "Learn more" }); + + expect(link).toHaveAttribute("href", DOCS_URLS.FINDINGS_INGESTION); + expect(link).toHaveAttribute("target", "_blank"); + expect(link).toHaveAttribute("rel", "noopener noreferrer"); + }); + + it("does not render when previously dismissed", () => { + localStorageMock.setItem(STORAGE_KEY, "true"); + + const { container } = render(); + + expect(container).toBeEmptyDOMElement(); + }); + + it("dismisses the banner and persists to localStorage on close", async () => { + const user = userEvent.setup(); + + render(); + + const closeButton = screen.getByRole("button", { name: "Close" }); + + await user.click(closeButton); + + expect( + screen.queryByText(/Import findings from Prowler CLI/), + ).not.toBeInTheDocument(); + expect(localStorageMock.setItem).toHaveBeenCalledWith(STORAGE_KEY, "true"); + }); + + it("renders with role='alert'", () => { + render(); + + expect(screen.getByRole("alert")).toBeInTheDocument(); + }); +}); diff --git a/ui/components/scans/cli-import-banner.tsx b/ui/components/scans/cli-import-banner.tsx new file mode 100644 index 0000000000..f566c4ae05 --- /dev/null +++ b/ui/components/scans/cli-import-banner.tsx @@ -0,0 +1,49 @@ +"use client"; + +import { Upload } from "lucide-react"; +import Link from "next/link"; +import { useState } from "react"; + +import { Alert, AlertTitle } from "@/components/shadcn"; +import { useMountEffect } from "@/hooks/use-mount-effect"; +import { DOCS_URLS } from "@/lib/external-urls"; +import { cn } from "@/lib/utils"; + +const STORAGE_KEY = "prowler:cli-import-banner-dismissed"; + +export const CliImportBanner = ({ className }: { className?: string }) => { + const [isVisible, setIsVisible] = useState(null); + + useMountEffect(() => { + const isDismissed = localStorage.getItem(STORAGE_KEY) === "true"; + setIsVisible(!isDismissed); + }); + + const handleClose = () => { + localStorage.setItem(STORAGE_KEY, "true"); + setIsVisible(false); + }; + + if (isVisible === null || !isVisible) return null; + + return ( + + + + Import findings from Prowler CLI —{" "} + + Learn more + + + + ); +}; diff --git a/ui/components/scans/index.ts b/ui/components/scans/index.ts index 9b28f6c2c8..a35e33e8d5 100644 --- a/ui/components/scans/index.ts +++ b/ui/components/scans/index.ts @@ -1 +1,2 @@ export * from "./auto-refresh"; +export * from "./cli-import-banner"; diff --git a/ui/components/scans/scans-filter-bar.test.tsx b/ui/components/scans/scans-filter-bar.test.tsx new file mode 100644 index 0000000000..f629e0291b --- /dev/null +++ b/ui/components/scans/scans-filter-bar.test.tsx @@ -0,0 +1,66 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; + +import { SCAN_JOBS_TAB } from "@/types"; + +import { ScansFilterBar } from "./scans-filter-bar"; + +vi.mock("@/components/filters/provider-account-selectors", () => ({ + ProviderAccountSelectors: () =>
Provider account selectors
, +})); + +vi.mock("@/components/shadcn", () => ({ + Select: ({ children }: { children: React.ReactNode }) => ( +
{children}
+ ), + SelectContent: ({ children }: { children: React.ReactNode }) => ( +
{children}
+ ), + SelectItem: ({ + children, + value, + }: { + children: React.ReactNode; + value: string; + }) =>
{children}
, + SelectTrigger: ({ children, ...props }: React.ComponentProps<"button">) => ( + + ), + SelectValue: ({ placeholder }: { placeholder: string }) => ( + {placeholder} + ), +})); + +const defaultProps = { + providers: [], + scheduleType: "all", + scanStatus: "all", + showStatusFilter: false, + onScheduleTypeChange: vi.fn(), + onScanStatusChange: vi.fn(), +}; + +describe("ScansFilterBar", () => { + it("hides the type filter on the scheduled tab", () => { + // Given + render( + , + ); + + // Then + expect( + screen.queryByRole("button", { name: /all types/i }), + ).not.toBeInTheDocument(); + expect(screen.getByText("Provider account selectors")).toBeInTheDocument(); + }); + + it("shows the type filter outside the scheduled tab", () => { + // Given + render( + , + ); + + // Then + expect(screen.getByRole("button", { name: /all types/i })).toBeVisible(); + }); +}); diff --git a/ui/components/scans/scans-filter-bar.tsx b/ui/components/scans/scans-filter-bar.tsx index 89ac753345..8ab48a8f9c 100644 --- a/ui/components/scans/scans-filter-bar.tsx +++ b/ui/components/scans/scans-filter-bar.tsx @@ -8,7 +8,7 @@ import { SelectTrigger, SelectValue, } from "@/components/shadcn"; -import type { ScanJobsTab } from "@/types"; +import { SCAN_JOBS_TAB, type ScanJobsTab } from "@/types"; import type { ProviderProps } from "@/types/providers"; import { @@ -40,6 +40,7 @@ export function ScansFilterBar({ const isCloudEnvironment = process.env.NEXT_PUBLIC_IS_CLOUD_ENV === "true"; const triggerFilterOptions = getScanTriggerFilterOptions(isCloudEnvironment); const statusFilterOptions = getScanStatusFilterOptions(activeTab); + const showScheduleTypeFilter = activeTab !== SCAN_JOBS_TAB.SCHEDULED; return ( <> @@ -52,18 +53,20 @@ export function ScansFilterBar({ accountSelectorClassName={filterItemClass} /> - + {showScheduleTypeFilter && ( + + )} {showStatusFilter && (