Threat Overview
Wiz Research linked nine publicly disclosed October AI developer-platform incidents to a wider pattern of attackers seeking environment variables and cloud/AI credentials. In two confirmed downstream enterprise AWS cases, keys previously used through a breached provider were validated within 30 minutes, later enumerated across models and quotas, and abused for approximately 800,000 and 1.7 million Bedrock calls. Wiz does not establish a single shared vulnerability, root cause or attacker for all nine incidents.
References
Wiz Research — Black October for AI Developer Platforms (October 30, 2026)
AWS — Amazon Bedrock CloudTrail logging
AWS — Bedrock Mantle CloudTrail logging
Impacted Systems
AWS accounts with Amazon Bedrock model-invocation permissions and credentials stored at or used through third-party AI development and hosting services. Cloud-managed AWS Bedrock and optional Bedrock Mantle. No vulnerable software version or patch is identified: exploitation requires stolen usable credentials with Bedrock permissions. Affected vendor incidents reported during October include Fireworks AI, Andromeda, Zeabur, Lightning AI, OpenCode, Railway, Warp, CommandCode and Northflank; root causes differ and investigations remain ongoing.
Why this matters
Stolen legitimate cloud credentials can generate major inference charges without malware on the customer’s endpoints. AWS CloudTrail can identify unusual principals, source IPs, model sweeps, quota discovery and sustained inference calls.
Exploitation Status
Wiz confirmed two downstream Bedrock abuse cases totaling approximately 2.5 million model calls. Wiz observed key-checking activity from 23.106.46.239, 38.246.114.118, 104.243.32.50 and 31.57.201.201, but explicitly states these IPs are not proven initial-intrusion infrastructure for every platform incident. Relationships between resale organizations and original intrusions are not fully established.
What this hunt looks for
High-volume inference bursts, newly observed credential/IP pairs, model and quota enumeration, inference permission failures, published stolen-key checking infrastructure and Mantle inference spikes where data events are collected.
Required logs
AWSCloudTrail in Microsoft Sentinel, including Bedrock management events. Bedrock Mantle CreateInference is a CloudTrail data event that requires explicit configuration; standard Bedrock InvokeModel/Converse calls are management events. A CloudTrail integration alone does not guarantee all AWS accounts or event classes are ingested.
01 — Sudden high-volume Bedrock model invocation by credential and IP
AWSCloudTrail
| where TimeGenerated >= ago(14d)
| where EventSource == "bedrock.amazonaws.com" and EventName in ("InvokeModel","InvokeModelWithResponseStream","Converse","ConverseStream")
| extend Principal=tostring(column_ifexists("UserIdentityArn", "")), SrcIP=tostring(column_ifexists("SourceIpAddress", "")), UA=tostring(column_ifexists("UserAgent", "")), Params=tostring(column_ifexists("RequestParameters", "")), EventTypeValue=tostring(column_ifexists("EventType", ""))
| where EventTypeValue == "" or EventTypeValue == "AwsApiCall"
| summarize Calls=count(), Models=make_set(extract(@'"modelId"\s*:\s*"([^"]+)',1,Params),15), Agents=make_set(UA,5) by Principal, SrcIP, bin(TimeGenerated,15m)
| where Calls >= 100
| order by Calls desc
02 — Newly observed IP for a Bedrock principal
let Base=AWSCloudTrail
| where TimeGenerated between (ago(30d) .. ago(1d))
| where EventSource == "bedrock.amazonaws.com" and EventName in ("InvokeModel","Converse","InvokeModelWithResponseStream","ConverseStream")
| extend Principal=tostring(column_ifexists("UserIdentityArn", "")), SrcIP=tostring(column_ifexists("SourceIpAddress", "")), UA=tostring(column_ifexists("UserAgent", "")), Params=tostring(column_ifexists("RequestParameters", "")), EventTypeValue=tostring(column_ifexists("EventType", ""))
| summarize by Principal, SrcIP;
AWSCloudTrail
| where TimeGenerated >= ago(1d)
| where EventSource == "bedrock.amazonaws.com" and EventName in ("InvokeModel","Converse","InvokeModelWithResponseStream","ConverseStream")
| extend Principal=tostring(column_ifexists("UserIdentityArn", "")), SrcIP=tostring(column_ifexists("SourceIpAddress", "")), UA=tostring(column_ifexists("UserAgent", "")), Params=tostring(column_ifexists("RequestParameters", "")), EventTypeValue=tostring(column_ifexists("EventType", ""))
| join kind=leftanti Base on Principal, SrcIP
| summarize Calls=count(), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), UserAgents=make_set(UA,5) by Principal, SrcIP
| order by Calls desc
03 — Published stolen-key checking infrastructure
let IOCs=dynamic(["23.106.46.239","38.246.114.118","104.243.32.50","31.57.201.201"]);
AWSCloudTrail
| where TimeGenerated >= ago(30d)
| extend Principal=tostring(column_ifexists("UserIdentityArn", "")), SrcIP=tostring(column_ifexists("SourceIpAddress", "")), UA=tostring(column_ifexists("UserAgent", "")), Params=tostring(column_ifexists("RequestParameters", "")), EventTypeValue=tostring(column_ifexists("EventType", ""))
| where SrcIP in (IOCs)
| project TimeGenerated, EventSource, EventName, Principal, SrcIP, UA, Params, ErrorCode, AWSRegion
| order by TimeGenerated desc
04 — Bedrock model and quota enumeration
AWSCloudTrail
| where TimeGenerated >= ago(14d)
| where EventName in ("ListFoundationModels","ListInferenceProfiles","GetInferenceProfile","GetFoundationModel","GetServiceQuota","ListServiceQuotas","ListAWSDefaultServiceQuotas")
| extend Principal=tostring(column_ifexists("UserIdentityArn", "")), SrcIP=tostring(column_ifexists("SourceIpAddress", "")), UA=tostring(column_ifexists("UserAgent", "")), Params=tostring(column_ifexists("RequestParameters", "")), EventTypeValue=tostring(column_ifexists("EventType", ""))
| summarize Requests=count(), Operations=make_set(EventName,12), Regions=make_set(AWSRegion,10), UserAgents=make_set(UA,5) by Principal, SrcIP, bin(TimeGenerated,1h)
| where Requests >= 8 or array_length(Operations) >= 4
| order by Requests desc
05 — Multiple Bedrock models probed by one credential
AWSCloudTrail
| where TimeGenerated >= ago(14d)
| where EventSource == "bedrock.amazonaws.com" and EventName in ("InvokeModel","Converse","InvokeModelWithResponseStream","ConverseStream")
| extend Principal=tostring(column_ifexists("UserIdentityArn", "")), SrcIP=tostring(column_ifexists("SourceIpAddress", "")), UA=tostring(column_ifexists("UserAgent", "")), Params=tostring(column_ifexists("RequestParameters", "")), EventTypeValue=tostring(column_ifexists("EventType", ""))
| extend ModelId=tostring(parse_json(Params).modelId)
| where isnotempty(ModelId)
| summarize Calls=count(), DistinctModels=dcount(ModelId), Models=make_set(ModelId,20) by Principal, SrcIP, bin(TimeGenerated,1h)
| where DistinctModels >= 6
| order by DistinctModels desc
06 — Bedrock permission failures or denied inference profile probes
AWSCloudTrail
| where TimeGenerated >= ago(14d)
| where EventSource == "bedrock.amazonaws.com" and EventName in ("InvokeModel","Converse","InvokeModelWithResponseStream","ConverseStream","ListInferenceProfiles")
| extend Principal=tostring(column_ifexists("UserIdentityArn", "")), SrcIP=tostring(column_ifexists("SourceIpAddress", "")), UA=tostring(column_ifexists("UserAgent", "")), Params=tostring(column_ifexists("RequestParameters", "")), EventTypeValue=tostring(column_ifexists("EventType", ""))
| extend Err=tostring(column_ifexists("ErrorCode", ""))
| where isnotempty(Err)
| summarize Denied=count(), Errors=make_set(Err,10), ParamsSamples=make_set(substring(Params,0,180),5) by Principal, SrcIP, bin(TimeGenerated,30m)
| where Denied >= 10
| order by Denied desc
07 — Bedrock quota discovery followed by inference activity
let Quotas=AWSCloudTrail
| where TimeGenerated >= ago(14d)
| where EventName in ("GetServiceQuota","ListServiceQuotas","ListAWSDefaultServiceQuotas","ListInferenceProfiles")
| extend Principal=tostring(column_ifexists("UserIdentityArn", "")), SrcIP=tostring(column_ifexists("SourceIpAddress", "")), UA=tostring(column_ifexists("UserAgent", "")), Params=tostring(column_ifexists("RequestParameters", "")), EventTypeValue=tostring(column_ifexists("EventType", ""))
| summarize QuotaEvents=count(), QuotaFirst=min(TimeGenerated) by Principal, SrcIP, Bucket=bin(TimeGenerated,1h);
let Calls=AWSCloudTrail
| where TimeGenerated >= ago(14d)
| where EventSource == "bedrock.amazonaws.com" and EventName in ("InvokeModel","Converse","InvokeModelWithResponseStream","ConverseStream")
| extend Principal=tostring(column_ifexists("UserIdentityArn", "")), SrcIP=tostring(column_ifexists("SourceIpAddress", "")), UA=tostring(column_ifexists("UserAgent", "")), Params=tostring(column_ifexists("RequestParameters", "")), EventTypeValue=tostring(column_ifexists("EventType", ""))
| summarize InferenceCalls=count() by Principal, SrcIP, Bucket=bin(TimeGenerated,1h);
Quotas
| join kind=inner Calls on Principal, SrcIP, Bucket
| where InferenceCalls >= 50
| project Bucket, Principal, SrcIP, QuotaEvents, InferenceCalls, QuotaFirst
| order by InferenceCalls desc
08 — Bedrock Mantle inference burst (data-event coverage only)
AWSCloudTrail
| where TimeGenerated >= ago(14d)
| where EventSource == "bedrock-mantle.amazonaws.com" and EventName == "CreateInference"
| extend Principal=tostring(column_ifexists("UserIdentityArn", "")), SrcIP=tostring(column_ifexists("SourceIpAddress", "")), UA=tostring(column_ifexists("UserAgent", "")), Params=tostring(column_ifexists("RequestParameters", "")), EventTypeValue=tostring(column_ifexists("EventType", ""))
| where EventTypeValue == "" or EventTypeValue == "AwsApiCall"
| summarize Calls=count(), Agents=make_set(UA,5), Regions=make_set(AWSRegion,10) by Principal, SrcIP, bin(TimeGenerated,15m)
| where Calls >= 100
| order by Calls desc
Detection Notes
Highest signal is a new principal/IP combined with quota or model enumeration and a sustained inference burst. Thresholds need workload calibration. Streaming API completion events can duplicate invocation records; where EventType is available, count AwsApiCall only. Mantle uses bedrock-mantle.amazonaws.com and needs data-event capture. Wiz IPs are credential-checking/resale indicators, not confirmed initial access for all breaches. Missing AWSCloudTrail ingestion or omitted accounts prevents visibility. No customer-specific Sentinel query was run and no finding is asserted.