GCP Vertex AI Caller Impossible Travel
Description
Detects the same Vertex AI authenticated caller (client.user.email) generating aiplatform audit activity from geographically distant source IPs within a short window at an impossible travel speed. That pattern can indicate a stolen user or service-account credential used from multiple regions. Related: GCP Vertex AI Caller Activity From High Number of Countries covers slower multi-country reuse without a high-speed hop.
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*"
)
| eval
Esql.source_geo_lat = st_y(source.geo.location),
Esql.source_geo_lon = st_x(source.geo.location)
| where Esql.source_geo_lat is not null and Esql.source_geo_lon is not null
| stats
Esql.first_lat = first(Esql.source_geo_lat, @timestamp),
Esql.first_lon = first(Esql.source_geo_lon, @timestamp),
Esql.last_lat = last(Esql.source_geo_lat, @timestamp),
Esql.last_lon = last(Esql.source_geo_lon, @timestamp),
Esql.event_count = count(*),
Esql.country_count = count_distinct(source.geo.country_name),
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.event_action_values = values(event.action),
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.event_count >= 2 and Esql.country_count >= 2
| eval
Esql.p1 = to_geopoint(concat("POINT(", to_string(Esql.first_lon), " ", to_string(Esql.first_lat), ")")),
Esql.p2 = to_geopoint(concat("POINT(", to_string(Esql.last_lon), " ", to_string(Esql.last_lat), ")"))
| eval
Esql.distance_km = round(st_distance(Esql.p1, Esql.p2) / 1000.0, 0),
Esql.window_minutes = date_diff("minute", Esql.timestamp_first_seen, Esql.timestamp_last_seen),
Esql.travel_kmh = case(Esql.window_minutes > 0, round(Esql.distance_km * 60.0 / Esql.window_minutes, 0), null),
source.ip = MV_FIRST(Esql.source_ip_values)
| where Esql.distance_km >= 500 and Esql.travel_kmh >= 800
| keep
client.user.email,
source.ip,
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.event_action_values,
Esql.user_agent_original_values,
Esql.distance_km,
Esql.travel_kmh,
Esql.window_minutes,
Esql.country_count,
Esql.event_count,
Esql.timestamp_first_seen,
Esql.timestamp_last_seen
Investigation fields
Pivot points the source recommends for triage.
client.user.emailsource.ipEsql.distance_kmEsql.travel_kmhEsql.window_minutesEsql.country_countEsql.event_countEsql.source_ip_valuesEsql.source_geo_country_name_valuesEsql.source_geo_region_name_valuesEsql.source_geo_city_name_valuesEsql.source_as_organization_name_valuesEsql.event_action_valuesEsql.user_agent_original_valuesEsql.timestamp_first_seenEsql.timestamp_last_seen
Implementation guide
Requires GCP Vertex AI auditlogs with source.ip, source.geo.location, source.geo.country_name, and
client.user.email (mapped from protoPayload.authenticationInfo.principalEmail).
Known false positives
- Distributed CI or multi-region automation that shares one service account across distant egress IPs. Prefer per-region identities, or raise distance and speed thresholds.
- VPN or cloud egress that geolocates incorrectly or jumps between distant POPs. Validate the IP locations before rotating credentials.
Analyst notes
Investigating GCP Vertex AI Caller Impossible Travel
The same authenticated Vertex AI caller (client.user.email) generated aiplatform audit activity
from source IPs whose geolocation implies travel faster than the rule thresholds (default at least
500 km and 800 km/h) inside the lookback window. That pattern often indicates stolen user or
service-account credentials used promptly from multiple regions.
Possible investigation steps
- Review
Esql.source_ip_values,Esql.source_geo_country_name_values,Esql.source_geo_region_name_values,Esql.source_geo_city_name_values,Esql.distance_km,Esql.travel_kmh,Esql.window_minutes, andEsql.event_action_valueson the alert. - Determine whether
client.user.emailis a human user or a shared service account. Shared SAs used by multi-region automation are a common false positive. - Timeline-sort raw audit events for that email: compare IP, geo, user agent, and action. Look for privileged or unusual methods around the distant hop.
- Check GCP IAM / audit logs for key creation, key download, or role grants for that principal near the first distant event.
- Pivot to
logs-gcp_vertexai.prompt_response_logs-*in the same window for prompt content and token volume that might show abuse after credential theft. - If countries accumulate to three or more over a longer window without meeting the speed thresholds, review alerts from GCP Vertex AI Caller Activity From High Number of Countries.
False positive analysis
- Multi-region CI or automation that shares one service account across distant egress IPs. Prefer per-region identities, or raise distance/speed thresholds.
- VPN or cloud egress that geolocates incorrectly or jumps between distant POPs. Corroborate with
ASN/org (
Esql.source_as_organization_name_values) and consistent tooling user agents before rotating credentials.
Response and remediation
- If travel is not approved: disable or rotate the caller's keys/tokens, revoke active sessions, and review IAM bindings for unexpected grants.
- Split multi-region workloads onto region-scoped identities; enable alerts on new key creation for sensitive service accounts.