Detection rules › Elastic

GCP Secret Manager ListSecrets Across Multiple Projects

Status
production
Severity
high
Time window
6m
Group by
client.user.email, data_stream.namespace, source.ip
Author
Elastic
Source
github.com/elastic/detection-rules

Detects a single identity listing Google Cloud Secret Manager secrets across many distinct projects in a short window. ListSecrets does not return secret values, but sweeping many projects is a common reconnaissance step before targeted AccessSecretVersion calls. Legitimate workloads typically list secrets within one project or a small set of projects; cross-project bursts from one user and source IP are uncommon outside security tooling or compromise.

Known false positives

  • Organization-wide security scanners, CSPM products, inventory jobs, or approved red-team exercises may list secrets across many projects. Validate the principal, source IP, user agent, and schedule against known tooling before treating the activity as malicious, and exclude documented automation identities when baselined.
  • Platform or IAM administrators troubleshooting Secret Manager access across environments may briefly exceed the project cardinality threshold. Correlate with change tickets and expected administrative clients.

MITRE ATT&CK coverage

TacticTechniques
Discovery

Telemetry coverage

Rule body

[metadata]
creation_date = "2026/08/12"
integration = ["gcp"]
maturity = "production"
updated_date = "2026/08/12"

[rule]
author = ["Elastic"]
description = """
Detects a single identity listing Google Cloud Secret Manager secrets across many distinct projects in a short window.
ListSecrets does not return secret values, but sweeping many projects is a common reconnaissance step before targeted
AccessSecretVersion calls. Legitimate workloads typically list secrets within one project or a small set of projects;
cross-project bursts from one user and source IP are uncommon outside security tooling or compromise.
"""
false_positives = [
    """
    Organization-wide security scanners, CSPM products, inventory jobs, or approved red-team exercises may list secrets
    across many projects. Validate the principal, source IP, user agent, and schedule against known tooling before
    treating the activity as malicious, and exclude documented automation identities when baselined.
    """,
    """
    Platform or IAM administrators troubleshooting Secret Manager access across environments may briefly exceed the
    project cardinality threshold. Correlate with change tickets and expected administrative clients.
    """,
]
from = "now-6m"
interval = "5m"
language = "esql"
license = "Elastic License v2"
name = "GCP Secret Manager ListSecrets Across Multiple Projects"
note = """## Triage and analysis

### Investigating GCP Secret Manager ListSecrets Across Multiple Projects

This rule aggregates Secret Manager `ListSecrets` audit events per `client.user.email` and `source.ip` over the rule
lookback. It alerts when the same actor lists secrets in 10 or more distinct `cloud.project.id` values. Listing does
not retrieve secret payloads, but multi-project enumeration is a strong discovery signal ahead of credential access.

### Possible investigation steps

- Review `Esql.cloud_project_id_values` to identify which projects were
  enumerated and whether they include high-value or production workloads.
- Confirm whether `client.user.email`, `source.ip`, and
  `Esql.user_agent_original_values` match expected administrators, CI/CD, or approved security scanners.
- Check `Esql.event_outcome_values` for mixed success and failure, which can indicate permission probing across projects
  the identity cannot fully access.
- Hunt for follow-on Secret Manager activity from the same identity or IP, especially
  `AccessSecretVersion`, `GetSecret`, and IAM policy changes on secrets or projects.
- Bound the burst with `Esql.earliest_timestamp` and `Esql.latest_timestamp`, then pivot in Discover on the same
  `client.user.email` / `source.ip` for related GCP audit activity.

### False positive analysis

- Documented CSPM, secret inventory, or compliance scanners that walk many projects will match; exclude those
  principals after validation.
- Break-glass or org-admin troubleshooting can look similar; require change-management correlation before raising
  severity.

### Response and remediation

- If unauthorized, revoke or rotate the implicated credentials, review IAM bindings that grant
  `secretmanager.secrets.list` across projects, and inspect for subsequent secret access or exfiltration.
- Restrict Secret Manager list permissions to least privilege and prefer per-project roles over org-wide grants for
  human users.
"""
setup = """The GCP Fleet integration (or Filebeat module) with audit logs for Secret Manager is required. `ListSecrets` is a
data-access method; enable DATA_READ audit logging for the Secret Manager API so these events are ingested into
`logs-gcp.audit-*`.

See [Secret Manager audit logging](https://cloud.google.com/secret-manager/docs/audit-logging) and
[Configure Data Access audit logs](https://cloud.google.com/logging/docs/audit/configure-data-access).
"""
references = [
    "https://cloud.google.com/secret-manager/docs/reference/rest/v1/projects.secrets/list",
]
risk_score = 73
rule_id = "642ac343-e7d6-4cf2-bb0b-7db412e614ad"
severity = "high"
tags = [
    "Domain: Cloud",
    "Data Source: GCP",
    "Data Source: Google Cloud Platform",
    "Data Source: GCP Audit Logs",
    "Use Case: Threat Detection",
    "Tactic: Discovery",
    "Resources: Investigation Guide",
    "Rule Type: ESQL",
    "Platform: GCP",
    "Service: GCP Secret Manager",
]
timestamp_override = "event.ingested"
type = "esql"

query = '''
from logs-gcp.audit-* metadata _id, _version, _index
| where data_stream.dataset == "gcp.audit"
    and event.action == "google.cloud.secretmanager.v1.SecretManagerService.ListSecrets"
    and cloud.project.id is not null
    and client.user.email is not null
    and source.ip is not null
| stats
    Esql.cloud_project_id_count_distinct = count_distinct(cloud.project.id),
    Esql.cloud_project_id_values = values(cloud.project.id),
    Esql.event_count = count(*),
    Esql.event_outcome_values = values(event.outcome),
    Esql.client_user_id_values = values(client.user.id),
    Esql.user_agent_original_values = values(user_agent.original),
    Esql.earliest_timestamp = min(@timestamp),
    Esql.latest_timestamp = max(@timestamp)
  by client.user.email, source.ip, data_stream.namespace
| where Esql.cloud_project_id_count_distinct >= 10
| keep
    client.user.email,
    source.ip,
    Esql.cloud_project_id_count_distinct,
    Esql.cloud_project_id_values,
    Esql.event_count,
    Esql.event_outcome_values,
    Esql.client_user_id_values,
    Esql.user_agent_original_values,
    Esql.earliest_timestamp,
    Esql.latest_timestamp, 
    data_stream.namespace
'''

[[rule.threat]]
framework = "MITRE ATT&CK"

[[rule.threat.technique]]
id = "T1526"
name = "Cloud Service Discovery"
reference = "https://attack.mitre.org/techniques/T1526/"

[rule.threat.tactic]
id = "TA0007"
name = "Discovery"
reference = "https://attack.mitre.org/tactics/TA0007/"

Stages and Predicates

Stage 1: from

from logs-gcp.audit-* metadata _id, _version, _index

Stage 2: where

| where data_stream.dataset == "gcp.audit"
    and event.action == "google.cloud.secretmanager.v1.SecretManagerService.ListSecrets"
    and cloud.project.id is not null
    and client.user.email is not null
    and source.ip is not null

Stage 3: stats

| stats
    Esql.cloud_project_id_count_distinct = count_distinct(cloud.project.id),
    Esql.cloud_project_id_values = values(cloud.project.id),
    Esql.event_count = count(*),
    Esql.event_outcome_values = values(event.outcome),
    Esql.client_user_id_values = values(client.user.id),
    Esql.user_agent_original_values = values(user_agent.original),
    Esql.earliest_timestamp = min(@timestamp),
    Esql.latest_timestamp = max(@timestamp)
  by client.user.email, source.ip, data_stream.namespace

Stage 4: where

| where Esql.cloud_project_id_count_distinct >= 10

Stage 5: keep

| keep
    client.user.email,
    source.ip,
    Esql.cloud_project_id_count_distinct,
    Esql.cloud_project_id_values,
    Esql.event_count,
    Esql.event_outcome_values,
    Esql.client_user_id_values,
    Esql.user_agent_original_values,
    Esql.earliest_timestamp,
    Esql.latest_timestamp, 
    data_stream.namespace

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
client.user.emailKEEP client.user.email
source.ipKEEP source.ip
Esql.cloud_project_id_count_distinctKEEP Esql.cloud_project_id_count_distinct
Esql.cloud_project_id_valuesKEEP Esql.cloud_project_id_values
Esql.event_countKEEP Esql.event_count
Esql.event_outcome_valuesKEEP Esql.event_outcome_values
Esql.client_user_id_valuesKEEP Esql.client_user_id_values
Esql.user_agent_original_valuesKEEP Esql.user_agent_original_values
Esql.earliest_timestampKEEP Esql.earliest_timestamp
Esql.latest_timestampKEEP Esql.latest_timestamp
data_stream.namespaceKEEP data_stream.namespace