AWS Bedrock Model Invocation Impossible Travel

Last updated 5 days ago on 2026-10-23
Created 5 days ago on 2026-10-23

About

Detects the same AWS principal invoking Amazon Bedrock models (InvokeModel, Converse, and their streaming variants) from geographically distant source IPs within a short window at an impossible travel speed. Stolen access keys and session credentials are frequently resold or reused for LLMjacking, and an inference call from a second continent minutes after a legitimate one is a strong indicator that the credential is being used by someone else.
Tags
Domain: CloudDomain: GenAIData Source: AWSData Source: Amazon Web ServicesData Source: AWS CloudTrailPlatform: AWSService: AWS BedrockUse Case: Threat DetectionTactic: Credential AccessTactic: Initial AccessThreat: Impossible TravelThreat: LLMjackingThreat: Unauthorized AI UsageMitre Atlas: AML.T0012Mitre Atlas: AML.T0091Mitre Atlas: AML.T0091.000Rule Type: ES|QLLanguage: esql
Severity
medium
Risk Score
47
MITRE ATT&CK™

Credential Access (TA0006)(external, opens in a new tab or window)

Initial Access (TA0001)(external, opens in a new tab or window)

False Positive Examples
Multi-region automation or CI pipelines that share one IAM user or role session across distant egress IPs. Prefer per-region identities, or raise the distance and speed thresholds. VPN, proxy, or cloud egress that geolocates incorrectly or jumps between distant points of presence. Validate the IP locations and ASNs before rotating credentials.
License
Elastic License v2(external, opens in a new tab or window)

Definition

Integration Pack
Prebuilt Security Detection Rules
Related Integrations

aws(external, opens in a new tab or window)

Query
text code block:
from logs-aws.cloudtrail-* | where data_stream.dataset == "aws.cloudtrail" and event.provider == "bedrock.amazonaws.com" and event.action in ("InvokeModel", "InvokeModelWithResponseStream", "Converse", "ConverseStream") and event.outcome == "success" and aws.cloudtrail.user_identity.arn is not null and source.ip is not null and source.geo.location is not null and source.geo.country_name is not null | eval Esql.identity = coalesce( aws.cloudtrail.user_identity.session_context.session_issuer.arn, aws.cloudtrail.user_identity.arn ), 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_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.aws_cloudtrail_user_identity_arn_values = values(aws.cloudtrail.user_identity.arn), Esql.aws_cloudtrail_user_identity_type_values = values(aws.cloudtrail.user_identity.type), Esql.aws_cloudtrail_user_identity_access_key_id_values = values(aws.cloudtrail.user_identity.access_key_id), Esql.cloud_region_values = values(cloud.region), Esql.timestamp_first_seen = min(@timestamp), Esql.timestamp_last_seen = max(@timestamp) by Esql.identity, cloud.account.id | 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_seconds = date_diff("second", Esql.timestamp_first_seen, Esql.timestamp_last_seen), Esql.travel_kmh = case(Esql.window_seconds > 0, round(Esql.distance_km * 3600.0 / Esql.window_seconds, 0), null), source.ip = mv_first(Esql.source_ip_values) | where Esql.distance_km >= 500 and Esql.travel_kmh >= 800 | keep Esql.identity, cloud.account.id, source.ip, Esql.source_ip_values, Esql.source_geo_country_name_values, Esql.source_geo_city_name_values, Esql.source_as_organization_name_values, Esql.event_action_values, Esql.user_agent_original_values, Esql.aws_cloudtrail_user_identity_arn_values, Esql.aws_cloudtrail_user_identity_type_values, Esql.aws_cloudtrail_user_identity_access_key_id_values, Esql.cloud_region_values, Esql.distance_km, Esql.travel_kmh, Esql.window_seconds, Esql.country_count, Esql.event_count, Esql.timestamp_first_seen, Esql.timestamp_last_seen

Install detection rules in Elastic Security

Detect AWS Bedrock Model Invocation Impossible Travel in the Elastic Security detection engine by installing this rule into your Elastic Stack.

To setup this rule, check out the installation guide for Prebuilt Security Detection Rules(external, opens in a new tab or window).