GCP Vertex AI Caller Activity From High Number of Countries


Description

Detects Vertex AI authenticated caller activity for the same client.user.email from at least three countries and three source IPs within a four-hour interval. That pattern can indicate stolen user or service-account credentials reused from multiple regions, including VPN or cloud egress and return trips where first and last locations are close.

Query · esql

from logs-gcp_vertexai.auditlogs-*
| where
    data_stream.dataset == "gcp_vertexai.auditlogs" and
    client.user.email is not null and
    source.ip is not null and
    source.geo.location is not null and
    source.geo.country_name is not null and
    (
        event.action like "*PredictionService.GenerateContent*" or
        event.action like "*PredictionService.StreamGenerateContent*" or
        event.action like "*PredictionService.CountTokens*"
    )
| stats
    Esql.country_count = count_distinct(source.geo.country_name),
    Esql.source_ip_count = count_distinct(source.ip),
    Esql.user_agent_count = count_distinct(user_agent.original),
    Esql.event_count = count(*),
    Esql.event_action_values = values(event.action),
    Esql.source_ip_values = values(source.ip),
    Esql.source_geo_country_name_values = values(source.geo.country_name),
    Esql.source_geo_region_name_values = values(source.geo.region_name),
    Esql.source_geo_city_name_values = values(source.geo.city_name),
    Esql.source_as_organization_name_values = values(source.as.organization.name),
    Esql.user_agent_original_values = values(user_agent.original),
    Esql.timestamp_first_seen = min(@timestamp),
    Esql.timestamp_last_seen = max(@timestamp)
  by
    client.user.email
| where Esql.country_count >= 3 and Esql.source_ip_count >= 3
| eval source.ip = MV_FIRST(Esql.source_ip_values)
| keep
    client.user.email,
    source.ip,
    Esql.country_count,
    Esql.source_ip_count,
    Esql.user_agent_count,
    Esql.event_count,
    Esql.event_action_values,
    Esql.source_ip_values,
    Esql.source_geo_country_name_values,
    Esql.source_geo_region_name_values,
    Esql.source_geo_city_name_values,
    Esql.source_as_organization_name_values,
    Esql.user_agent_original_values,
    Esql.timestamp_first_seen,
    Esql.timestamp_last_seen

Investigation fields

Pivot points the source recommends for triage.

  • client.user.email
  • source.ip
  • Esql.country_count
  • Esql.source_ip_count
  • Esql.user_agent_count
  • Esql.event_count
  • Esql.event_action_values
  • Esql.source_ip_values
  • Esql.source_geo_country_name_values
  • Esql.source_geo_region_name_values
  • Esql.source_geo_city_name_values
  • Esql.source_as_organization_name_values
  • Esql.user_agent_original_values
  • Esql.timestamp_first_seen
  • Esql.timestamp_last_seen

Implementation guide

Requires GCP Vertex AI auditlogs with source.ip, source.geo.location, source.geo.country_name, client.user.email, and preferably user_agent.original.

Known false positives

  • Shared service accounts used by multi-region CI, build agents, or dual-homed VPN/cloud egress can span several countries. Prefer per-region identities, or exclude known automation principals.
  • VPN or proxy egress that geo-resolves through hubs far from the caller's physical location. Corroborate with ASN and user-agent diversity before rotating credentials.

Analyst notes

Investigating GCP Vertex AI Caller Activity From High Number of Countries

Successful Vertex AI GenerateContent / CountTokens audit activity for the same client.user.email spans at least three countries and three source IPs inside four hours. That pattern can indicate leaked or stolen credentials reused from VPN or cloud egress in multiple regions, including return trips where the first and last locations are close.

Escalate when countries differ, user agents diverge (SDK vs curl/browser), privileged methods appear on the distant IPs, or IAM key activity precedes the burst. Close as a false positive for documented multi-region automation with consistent corporate tooling.

Possible investigation steps

  • Use Esql.source_geo_country_name_values, Esql.source_geo_region_name_values, Esql.source_ip_values, and Esql.user_agent_original_values to map the geographic and client spread.
  • Use Esql.event_action_values and Esql.event_count to see what the caller did.
  • Contact the owner of client.user.email (or the owning team for a service account) to confirm whether VPN or multi-region automation is expected.
  • Pivot to logs-gcp_vertexai.prompt_response_logs-* in the same window for prompt abuse, refusals, or token volume after the distant events.
  • Check GCP IAM / audit logs for key creation, key download, or role grants near the first distant country.

False positive analysis

  • Multi-region CI and dual-homed VPN/cloud egress are the main FPs — require ASN/UA corroboration and known-principal allowlists before rotating credentials.
  • Shared org-wide service accounts used from many regions by design. Split those identities per region or exclude them.

Response and remediation

  • On suspected compromise: disable or rotate the caller's keys/tokens, revoke active sessions, hunt concurrent sessions from the listed IPs, and review IAM bindings after the distant events.
  • Prefer region-scoped service accounts for automation so legitimate multi-region traffic does not share one email principal.
Raw source GCP Vertex AI Caller Activity From High Number of Countries · Elastic TOML
Esc
Published by elastic/detection-rules ↗, licensed under Elastic License 2.0 ↗. Reproduced here unmodified.
[metadata]
creation_date = "2026/10/05"
integration = ["gcp_vertexai"]
maturity = "production"
min_stack_comments = "Requires GCP Vertex AI auditlogs enriched with source.geo fields."
min_stack_version = "9.4.0"
updated_date = "2026/10/06"

[rule]
author = ["Elastic"]
description = """
Detects Vertex AI authenticated caller activity for the same client.user.email from at least three countries and three
source IPs within a four-hour interval. That pattern can indicate stolen user or service-account credentials reused
from multiple regions, including VPN or cloud egress and return trips where first and last locations are close.
"""
false_positives = [
    """
    Shared service accounts used by multi-region CI, build agents, or dual-homed VPN/cloud egress can span several
    countries. Prefer per-region identities, or exclude known automation principals.
    """,
    """
    VPN or proxy egress that geo-resolves through hubs far from the caller's physical location. Corroborate with ASN
    and user-agent diversity before rotating credentials.
    """,
]
from = "now-4h"
interval = "15m"
language = "esql"
license = "Elastic License v2"
name = "GCP Vertex AI Caller Activity From High Number of Countries"
note = """## Triage and analysis

### Investigating GCP Vertex AI Caller Activity From High Number of Countries

Successful Vertex AI GenerateContent / CountTokens audit activity for the same
`client.user.email` spans at least three countries and three source IPs inside four hours.
That pattern can indicate leaked or stolen credentials reused from VPN or cloud egress in
multiple regions, including return trips where the first and last locations are close.

Escalate when countries differ, user agents diverge (SDK vs curl/browser), privileged methods
appear on the distant IPs, or IAM key activity precedes the burst. Close as a false positive for
documented multi-region automation with consistent corporate tooling.

#### Possible investigation steps

- Use `Esql.source_geo_country_name_values`, `Esql.source_geo_region_name_values`, `Esql.source_ip_values`, and
  `Esql.user_agent_original_values` to map the geographic and client spread.
- Use `Esql.event_action_values` and `Esql.event_count` to see what the caller did.
- Contact the owner of `client.user.email` (or the owning team for a service account) to confirm
  whether VPN or multi-region automation is expected.
- Pivot to `logs-gcp_vertexai.prompt_response_logs-*` in the same window for prompt abuse,
  refusals, or token volume after the distant events.
- Check GCP IAM / audit logs for key creation, key download, or role grants near the first
  distant country.

### False positive analysis

- Multi-region CI and dual-homed VPN/cloud egress are the main FPs — require ASN/UA
  corroboration and known-principal allowlists before rotating credentials.
- Shared org-wide service accounts used from many regions by design. Split those identities
  per region or exclude them.

### Response and remediation

- On suspected compromise: disable or rotate the caller's keys/tokens, revoke active sessions,
  hunt concurrent sessions from the listed IPs, and review IAM bindings after the distant events.
- Prefer region-scoped service accounts for automation so legitimate multi-region traffic does
  not share one email principal.
"""
references = [
    "https://www.elastic.co/docs/reference/integrations/gcp_vertexai",
]
risk_score = 99
rule_id = "b29f8041-5da1-40e4-aeee-607422074b29"
setup = """## Setup

Requires GCP Vertex AI `auditlogs` with `source.ip`, `source.geo.location`, `source.geo.country_name`,
`client.user.email`, and preferably `user_agent.original`.
"""
severity = "critical"
tags = [
    "Domain: GenAI",
    "Domain: Cloud",
    "Data Source: GCP Vertex AI",
    "Data Source: GCP",
    "Data Source: Google Cloud Platform",
    "Platform: GCP",
    "Service: GCP Vertex AI",
    "Use Case: Threat Detection",
    "Tactic: Credential Access",
    "Tactic: Initial Access",
    "Threat: Impossible Travel",
    "Threat: LLMjacking",
    "Threat: Unauthorized AI Usage",
    "Mitre Atlas: AML.T0012",
    "Mitre Atlas: AML.T0091",
    "Mitre Atlas: AML.T0091.000",
    "Resources: Investigation Guide",
    "Rule Type: ES|QL",
]
timestamp_override = "event.ingested"
type = "esql"

query = '''
from logs-gcp_vertexai.auditlogs-*
| where
    data_stream.dataset == "gcp_vertexai.auditlogs" and
    client.user.email is not null and
    source.ip is not null and
    source.geo.location is not null and
    source.geo.country_name is not null and
    (
        event.action like "*PredictionService.GenerateContent*" or
        event.action like "*PredictionService.StreamGenerateContent*" or
        event.action like "*PredictionService.CountTokens*"
    )
| stats
    Esql.country_count = count_distinct(source.geo.country_name),
    Esql.source_ip_count = count_distinct(source.ip),
    Esql.user_agent_count = count_distinct(user_agent.original),
    Esql.event_count = count(*),
    Esql.event_action_values = values(event.action),
    Esql.source_ip_values = values(source.ip),
    Esql.source_geo_country_name_values = values(source.geo.country_name),
    Esql.source_geo_region_name_values = values(source.geo.region_name),
    Esql.source_geo_city_name_values = values(source.geo.city_name),
    Esql.source_as_organization_name_values = values(source.as.organization.name),
    Esql.user_agent_original_values = values(user_agent.original),
    Esql.timestamp_first_seen = min(@timestamp),
    Esql.timestamp_last_seen = max(@timestamp)
  by
    client.user.email
| where Esql.country_count >= 3 and Esql.source_ip_count >= 3
| eval source.ip = MV_FIRST(Esql.source_ip_values)
| keep
    client.user.email,
    source.ip,
    Esql.country_count,
    Esql.source_ip_count,
    Esql.user_agent_count,
    Esql.event_count,
    Esql.event_action_values,
    Esql.source_ip_values,
    Esql.source_geo_country_name_values,
    Esql.source_geo_region_name_values,
    Esql.source_geo_city_name_values,
    Esql.source_as_organization_name_values,
    Esql.user_agent_original_values,
    Esql.timestamp_first_seen,
    Esql.timestamp_last_seen
'''


[[rule.threat]]
framework = "MITRE ATT&CK"
[[rule.threat.technique]]
id = "T1528"
name = "Steal Application Access Token"
reference = "https://attack.mitre.org/techniques/T1528/"


[rule.threat.tactic]
id = "TA0006"
name = "Credential Access"
reference = "https://attack.mitre.org/tactics/TA0006/"
[[rule.threat]]
framework = "MITRE ATT&CK"
[[rule.threat.technique]]
id = "T1078"
name = "Valid Accounts"
reference = "https://attack.mitre.org/techniques/T1078/"
[[rule.threat.technique.subtechnique]]
id = "T1078.004"
name = "Cloud Accounts"
reference = "https://attack.mitre.org/techniques/T1078/004/"



[rule.threat.tactic]
id = "TA0001"
name = "Initial Access"
reference = "https://attack.mitre.org/tactics/TA0001/"
[[rule.threat_mappings]]
framework = "MITRE ATLAS"
version = "2026.08"
[[rule.threat_mappings.threat]]
framework = "MITRE ATLAS"
[[rule.threat_mappings.threat.technique]]
id = "AML.T0012"
name = "Valid Accounts"
reference = "https://atlas.mitre.org/techniques/AML.T0012/"


[rule.threat_mappings.threat.tactic]
id = "AML.TA0004"
name = "Initial Access"
reference = "https://atlas.mitre.org/tactics/AML.TA0004/"
[[rule.threat_mappings.threat]]
framework = "MITRE ATLAS"
[[rule.threat_mappings.threat.technique]]
id = "AML.T0091"
name = "Use Alternate Authentication Material"
reference = "https://atlas.mitre.org/techniques/AML.T0091/"
[[rule.threat_mappings.threat.technique.subtechnique]]
id = "AML.T0091.000"
name = "Application Access Token"
reference = "https://atlas.mitre.org/techniques/AML.T0091.000/"



[rule.threat_mappings.threat.tactic]
id = "AML.TA0015"
name = "Lateral Movement"
reference = "https://atlas.mitre.org/tactics/AML.TA0015/"

[rule.investigation_fields]
field_names = [
    "client.user.email",
    "source.ip",
    "Esql.country_count",
    "Esql.source_ip_count",
    "Esql.user_agent_count",
    "Esql.event_count",
    "Esql.event_action_values",
    "Esql.source_ip_values",
    "Esql.source_geo_country_name_values",
    "Esql.source_geo_region_name_values",
    "Esql.source_geo_city_name_values",
    "Esql.source_as_organization_name_values",
    "Esql.user_agent_original_values",
    "Esql.timestamp_first_seen",
    "Esql.timestamp_last_seen",
]

[rule.alert_suppression]
group_by = ["client.user.email"]
missing_fields_strategy = "suppress"

[rule.alert_suppression.duration]
unit = "h"
value = 4

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.