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, andEsql.window_seconds. - Identify the credential type from
Esql.aws_cloudtrail_user_identity_type_valuesandEsql.aws_cloudtrail_user_identity_access_key_id_values. Long-termAKIAkeys used from two continents are a stronger signal than a shared role session. - Compare
Esql.user_agent_original_valuesacross 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
- AWS Bedrock Model Invocation From High Number of Countries
- GCP Vertex AI Caller Activity From High Number of Countries
- GCP Vertex AI Caller Impossible Travel
- AWS Bedrock AgentCore Execution Role Used Outside Its Runtime
- AWS Bedrock Unauthorized Foundation Model Access Attempt