AWS Bedrock Model Invocation Impossible Travel

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.

Elastic rule (View on GitHub)

  1[metadata]
  2creation_date = "2026/10/23"
  3integration = ["aws"]
  4maturity = "production"
  5min_stack_comments = "ES|QL FIRST/LAST aggregations and st_distance require 9.4.0+."
  6min_stack_version = "9.4.0"
  7updated_date = "2026/10/23"
  8
  9[rule]
 10author = ["Elastic"]
 11description = """
 12Detects the same AWS principal invoking Amazon Bedrock models (InvokeModel, Converse, and their streaming variants) from
 13geographically distant source IPs within a short window at an impossible travel speed. Stolen access keys and session
 14credentials are frequently resold or reused for LLMjacking, and an inference call from a second continent minutes after
 15a legitimate one is a strong indicator that the credential is being used by someone else.
 16"""
 17false_positives = [
 18    """
 19    Multi-region automation or CI pipelines that share one IAM user or role session across distant egress IPs. Prefer
 20    per-region identities, or raise the distance and speed thresholds.
 21    """,
 22    """
 23    VPN, proxy, or cloud egress that geolocates incorrectly or jumps between distant points of presence. Validate the IP
 24    locations and ASNs before rotating credentials.
 25    """,
 26]
 27from = "now-60m"
 28interval = "10m"
 29language = "esql"
 30license = "Elastic License v2"
 31name = "AWS Bedrock Model Invocation Impossible Travel"
 32note = """## Triage and analysis
 33
 34### Investigating AWS Bedrock Model Invocation Impossible Travel
 35
 36The same AWS principal (role issuer ARN when present, otherwise `aws.cloudtrail.user_identity.arn`) invoked Amazon
 37Bedrock models from source IPs whose geolocation implies travel faster than the rule thresholds (default at least
 38500 km and 800 km/h) inside the lookback window. Assumed-role sessions are grouped by the issuing role so re-assumes
 39of the same role still correlate. Bedrock runtime calls are CloudTrail management events, so this works without model
 40invocation logging.
 41
 42#### Possible investigation steps
 43
 44- Review `Esql.source_ip_values`, `Esql.source_geo_country_name_values`, `Esql.source_geo_city_name_values`,
 45  `Esql.source_as_organization_name_values`, `Esql.distance_km`, `Esql.travel_kmh`, and `Esql.window_seconds`.
 46- Identify the credential type from `Esql.aws_cloudtrail_user_identity_type_values` and
 47  `Esql.aws_cloudtrail_user_identity_access_key_id_values`. Long-term `AKIA` keys used from two continents are a
 48  stronger signal than a shared role session.
 49- Compare `Esql.user_agent_original_values` across the two locations. A change from an SDK or console agent to a
 50  generic HTTP client or LLM proxy is common in LLMjacking.
 51- Search CloudTrail for the same principal around the distant event for `ListFoundationModels`,
 52  `GetFoundationModelAvailability`, model access changes, or guardrail and logging changes.
 53- If Bedrock model invocation logging is enabled, review prompts and token volume from the unfamiliar location.
 54
 55### False positive analysis
 56
 57- Shared credentials used by multi-region workloads or CI runners. Move these workloads to region-scoped roles.
 58- VPN or proxy egress that geolocates inconsistently. Corroborate with ASN and user agent before acting.
 59
 60### Response and remediation
 61
 62- If the travel is not explained, deactivate the access key or revoke the role session, rotate credentials, and
 63  review IAM policies that grant `bedrock:InvokeModel*`.
 64- Review Bedrock usage and billing for the period and enable service quotas or budgets to cap abuse.
 65"""
 66references = [
 67    "https://docs.aws.amazon.com/bedrock/latest/userguide/logging-using-cloudtrail.html",
 68    "https://www.elastic.co/docs/reference/integrations/aws/cloudtrail",
 69]
 70risk_score = 47
 71rule_id = "94025e72-d8bf-499d-967b-eb479d36afc8"
 72setup = """## Setup
 73
 74This rule requires AWS CloudTrail management events ingested via the AWS integration (`aws.cloudtrail` data stream)
 75with `source.geo.location` and `source.geo.country_name` populated by the integration's GeoIP enrichment. Bedrock
 76runtime operations are logged as management events by default; no Bedrock model invocation logging is required.
 77"""
 78severity = "medium"
 79tags = [
 80    "Domain: Cloud",
 81    "Domain: GenAI",
 82    "Data Source: AWS",
 83    "Data Source: Amazon Web Services",
 84    "Data Source: AWS CloudTrail",
 85    "Platform: AWS",
 86    "Service: AWS Bedrock",
 87    "Use Case: Threat Detection",
 88    "Tactic: Credential Access",
 89    "Tactic: Initial Access",
 90    "Threat: Impossible Travel",
 91    "Threat: LLMjacking",
 92    "Threat: Unauthorized AI Usage",
 93    "Mitre Atlas: AML.T0012",
 94    "Mitre Atlas: AML.T0091",
 95    "Mitre Atlas: AML.T0091.000",
 96    "Resources: Investigation Guide",
 97    "Rule Type: ES|QL",
 98]
 99timestamp_override = "event.ingested"
100type = "esql"
101
102query = '''
103from logs-aws.cloudtrail-*
104| where
105    data_stream.dataset == "aws.cloudtrail" and
106    event.provider == "bedrock.amazonaws.com" and
107    event.action in ("InvokeModel", "InvokeModelWithResponseStream", "Converse", "ConverseStream") and
108    event.outcome == "success" and
109    aws.cloudtrail.user_identity.arn is not null and
110    source.ip is not null and
111    source.geo.location is not null and
112    source.geo.country_name is not null
113| eval
114    Esql.identity = coalesce(
115        aws.cloudtrail.user_identity.session_context.session_issuer.arn,
116        aws.cloudtrail.user_identity.arn
117    ),
118    Esql.source_geo_lat = st_y(source.geo.location),
119    Esql.source_geo_lon = st_x(source.geo.location)
120| where Esql.source_geo_lat is not null and Esql.source_geo_lon is not null
121| stats
122    Esql.first_lat = first(Esql.source_geo_lat, @timestamp),
123    Esql.first_lon = first(Esql.source_geo_lon, @timestamp),
124    Esql.last_lat = last(Esql.source_geo_lat, @timestamp),
125    Esql.last_lon = last(Esql.source_geo_lon, @timestamp),
126    Esql.event_count = count(*),
127    Esql.country_count = count_distinct(source.geo.country_name),
128    Esql.source_ip_values = values(source.ip),
129    Esql.source_geo_country_name_values = values(source.geo.country_name),
130    Esql.source_geo_city_name_values = values(source.geo.city_name),
131    Esql.source_as_organization_name_values = values(source.as.organization.name),
132    Esql.event_action_values = values(event.action),
133    Esql.user_agent_original_values = values(user_agent.original),
134    Esql.aws_cloudtrail_user_identity_arn_values = values(aws.cloudtrail.user_identity.arn),
135    Esql.aws_cloudtrail_user_identity_type_values = values(aws.cloudtrail.user_identity.type),
136    Esql.aws_cloudtrail_user_identity_access_key_id_values = values(aws.cloudtrail.user_identity.access_key_id),
137    Esql.cloud_region_values = values(cloud.region),
138    Esql.timestamp_first_seen = min(@timestamp),
139    Esql.timestamp_last_seen = max(@timestamp)
140  by
141    Esql.identity,
142    cloud.account.id
143| where Esql.event_count >= 2 and Esql.country_count >= 2
144| eval
145    Esql.p1 = to_geopoint(concat("POINT(", to_string(Esql.first_lon), " ", to_string(Esql.first_lat), ")")),
146    Esql.p2 = to_geopoint(concat("POINT(", to_string(Esql.last_lon), " ", to_string(Esql.last_lat), ")"))
147| eval
148    Esql.distance_km = round(st_distance(Esql.p1, Esql.p2) / 1000.0, 0),
149    Esql.window_seconds = date_diff("second", Esql.timestamp_first_seen, Esql.timestamp_last_seen),
150    Esql.travel_kmh = case(Esql.window_seconds > 0, round(Esql.distance_km * 3600.0 / Esql.window_seconds, 0), null),
151    source.ip = mv_first(Esql.source_ip_values)
152| where Esql.distance_km >= 500 and Esql.travel_kmh >= 800
153| keep
154    Esql.identity,
155    cloud.account.id,
156    source.ip,
157    Esql.source_ip_values,
158    Esql.source_geo_country_name_values,
159    Esql.source_geo_city_name_values,
160    Esql.source_as_organization_name_values,
161    Esql.event_action_values,
162    Esql.user_agent_original_values,
163    Esql.aws_cloudtrail_user_identity_arn_values,
164    Esql.aws_cloudtrail_user_identity_type_values,
165    Esql.aws_cloudtrail_user_identity_access_key_id_values,
166    Esql.cloud_region_values,
167    Esql.distance_km,
168    Esql.travel_kmh,
169    Esql.window_seconds,
170    Esql.country_count,
171    Esql.event_count,
172    Esql.timestamp_first_seen,
173    Esql.timestamp_last_seen
174'''
175
176
177[[rule.threat]]
178framework = "MITRE ATT&CK"
179[[rule.threat.technique]]
180id = "T1528"
181name = "Steal Application Access Token"
182reference = "https://attack.mitre.org/techniques/T1528/"
183
184
185[rule.threat.tactic]
186id = "TA0006"
187name = "Credential Access"
188reference = "https://attack.mitre.org/tactics/TA0006/"
189[[rule.threat]]
190framework = "MITRE ATT&CK"
191[[rule.threat.technique]]
192id = "T1078"
193name = "Valid Accounts"
194reference = "https://attack.mitre.org/techniques/T1078/"
195[[rule.threat.technique.subtechnique]]
196id = "T1078.004"
197name = "Cloud Accounts"
198reference = "https://attack.mitre.org/techniques/T1078/004/"
199
200
201
202[rule.threat.tactic]
203id = "TA0001"
204name = "Initial Access"
205reference = "https://attack.mitre.org/tactics/TA0001/"
206[[rule.threat_mappings]]
207framework = "MITRE ATLAS"
208version = "2026.09"
209[[rule.threat_mappings.threat]]
210framework = "MITRE ATLAS"
211[[rule.threat_mappings.threat.technique]]
212id = "AML.T0012"
213name = "Valid Accounts"
214reference = "https://atlas.mitre.org/techniques/AML.T0012/"
215
216
217[rule.threat_mappings.threat.tactic]
218id = "AML.TA0004"
219name = "Initial Access"
220reference = "https://atlas.mitre.org/tactics/AML.TA0004/"
221[[rule.threat_mappings.threat]]
222framework = "MITRE ATLAS"
223[[rule.threat_mappings.threat.technique]]
224id = "AML.T0091"
225name = "Use Alternate Authentication Material"
226reference = "https://atlas.mitre.org/techniques/AML.T0091/"
227[[rule.threat_mappings.threat.technique.subtechnique]]
228id = "AML.T0091.000"
229name = "Application Access Token"
230reference = "https://atlas.mitre.org/techniques/AML.T0091.000/"
231
232
233
234[rule.threat_mappings.threat.tactic]
235id = "AML.TA0015"
236name = "Lateral Movement"
237reference = "https://atlas.mitre.org/tactics/AML.TA0015/"
238
239[rule.alert_suppression]
240group_by = ["Esql.identity"]
241missing_fields_strategy = "suppress"
242
243[rule.investigation_fields]
244field_names = [
245    "Esql.identity",
246    "cloud.account.id",
247    "source.ip",
248    "Esql.distance_km",
249    "Esql.travel_kmh",
250    "Esql.window_seconds",
251    "Esql.country_count",
252    "Esql.event_count",
253    "Esql.source_ip_values",
254    "Esql.source_geo_country_name_values",
255    "Esql.source_geo_city_name_values",
256    "Esql.source_as_organization_name_values",
257    "Esql.user_agent_original_values",
258    "Esql.aws_cloudtrail_user_identity_arn_values",
259    "Esql.aws_cloudtrail_user_identity_access_key_id_values",
260]
261
262[rule.alert_suppression.duration]
263unit = "h"
264value = 4

Triage and analysis

Investigating AWS Bedrock Model Invocation Impossible Travel

The same AWS principal (role issuer ARN when present, otherwise aws.cloudtrail.user_identity.arn) invoked Amazon Bedrock models 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. Assumed-role sessions are grouped by the issuing role so re-assumes of the same role still correlate. Bedrock runtime calls are CloudTrail management events, so this works without model invocation logging.

Possible investigation steps

  • Review Esql.source_ip_values, Esql.source_geo_country_name_values, Esql.source_geo_city_name_values, Esql.source_as_organization_name_values, Esql.distance_km, Esql.travel_kmh, and Esql.window_seconds.
  • Identify the credential type from Esql.aws_cloudtrail_user_identity_type_values and Esql.aws_cloudtrail_user_identity_access_key_id_values. Long-term AKIA keys used from two continents are a stronger signal than a shared role session.
  • Compare Esql.user_agent_original_values across the two locations. A change from an SDK or console agent to a generic HTTP client or LLM proxy is common in LLMjacking.
  • Search CloudTrail for the same principal around the distant event for ListFoundationModels, GetFoundationModelAvailability, model access changes, or guardrail and logging changes.
  • If Bedrock model invocation logging is enabled, review prompts and token volume from the unfamiliar location.

False positive analysis

  • Shared credentials used by multi-region workloads or CI runners. Move these workloads to region-scoped roles.
  • VPN or proxy egress that geolocates inconsistently. Corroborate with ASN and user agent before acting.

Response and remediation

  • If the travel is not explained, deactivate the access key or revoke the role session, rotate credentials, and review IAM policies that grant bedrock:InvokeModel*.
  • Review Bedrock usage and billing for the period and enable service quotas or budgets to cap abuse.

References

Related rules

to-top