#1725·ScoutSuite

GCP Monitoring Alert Policy checks always return `false` due to unfilled placeholder

Author: adambarnett52Created Mar 23, 2026Updated Mar 23, 2026
Labelsbugpotential

ScoutSuite version

5.14.0 (latest)

Cloud provider

GCP

Description

MonitoringAlertPolicies._specific_alert_policy_present() in ScoutSuite/providers/gcp/resources/stackdrivermonitoring/monitoring_alert_policies.py always returns False for every check, causing the following findings to be permanently flagged regardless of what alert policies exist in the project:

  • Alerts Doesn't Exist for Audit Configuration Changes
  • Alerts Doesn't Exist for Cloud Storage IAM Permission Changes
  • Alerts Doesn't Exist for Custom Role Changes
  • Alerts Doesn't Exist for Project Ownership Assignment/Changes
  • Alerts Doesn't Exist for SQL Instance Configuration Changes
  • Alerts Doesn't Exist for VPC Network Changes
  • Alerts Doesn't Exist for VPC Network Firewall Rule Changes
  • Alerts Doesn't Exist for VPC Network Route Changes

Root cause

There are two bugs in _parse_alert_policy and _specific_alert_policy_present:

1. Unfilled placeholder

The filter comparison hardcodes a literal placeholder string that will never match a real alert policy:

python
if condition.condition_threshold.filter == 'metric.type=\"logging.googleapis.com/user/<Log Metric Name>\"'

<Log Metric Name> is never substituted with an actual metric name, so this condition can never be True.

2. All 8 checks call the same function with no arguments

Every dict key in _parse_alert_policy calls self._specific_alert_policy_present(raw_alert_policies) with no way to distinguish which metric is being checked, so even with the placeholder fixed, all 8 checks would return the same result.

Fix

Pass the specific metric name into _specific_alert_policy_present per check, and use a substring match instead of equality (to handle filters that include additional clauses such as AND resource.type=...):

python
def _parse_alert_policy(self, raw_alert_policies):
    alert_policy_dict = {}
    alert_policy_dict['project_ownership_assignments'] = \
        self._specific_alert_policy_present(raw_alert_policies, 'project_ownership_changes-counter')
    alert_policy_dict['audit_config_change'] = \
        self._specific_alert_policy_present(raw_alert_policies, 'audit_config_change-counter')
    alert_policy_dict['custom_role_change'] = \
        self._specific_alert_policy_present(raw_alert_policies, 'custom_role_changes-counter')
    alert_policy_dict['vpc_network_firewall_rule_change'] = \
        self._specific_alert_policy_present(raw_alert_policies, 'vpc_firewall_network_changes-counter')
    alert_policy_dict['vpc_network_route_change'] = \
        self._specific_alert_policy_present(raw_alert_policies, 'vpc_network_route_changes-counter')
    alert_policy_dict['vpc_network_change'] = \
        self._specific_alert_policy_present(raw_alert_policies, 'vpc_network_configuration_changes-counter')
    alert_policy_dict['cloud_storage_iam_permission_change'] = \
        self._specific_alert_policy_present(raw_alert_policies, 'cloud_storage_iam_changes-counter')
    alert_policy_dict['sql_instance_conf_change'] = \
        self._specific_alert_policy_present(raw_alert_policies, 'sql_instance_configuration_changes-counter')
    return alert_policy_dict

def _specific_alert_policy_present(self, alert_policies, metric_name):
    expected_fragment = 'logging.googleapis.com/user/{}'.format(metric_name)
    for alert_policy in alert_policies:
        if not alert_policy.enabled.value:
            continue
        for condition in alert_policy.conditions:
            if expected_fragment in condition.condition_threshold.filter:
                return True
    return False

Notes

The metric names used in the fix above follow the CIS-recommended naming convention. The fix uses a substring match (in) rather than equality (==) to remain compatible with alert policy filters that include additional restrictions such as AND resource.type="global", which the GCP Monitoring API requires in certain cases.