// Illustrative; not executed against a live workspace. Microsoft Sentinel: AWSCloudTrail (Amazon Web Services // connector). Inference theft: a key or role that lists or unlocks models, or deletes invocation logging, and // invokes models from an address it has not used in the previous 30 days. TrendAI's second Japan case and // Symantec's "Skynet mode" agent both went after cheap inference. Whether model invocations reach CloudTrail // depends on how your trail is configured; check that before you read an empty result as a clean one. let Window = 7d; let Baseline = 30d; let Recon = dynamic(["ListFoundationModels", "GetModelInvocationLoggingConfiguration"]); let Unlock = dynamic(["PutUseCaseForModelAccess", "CreateFoundationModelAgreement", "PutFoundationModelEntitlement"]); let Blind = dynamic(["DeleteModelInvocationLoggingConfiguration"]); let Invoke = dynamic(["InvokeModel", "InvokeModelWithResponseStream", "Converse", "ConverseStream"]); let Bedrock = AWSCloudTrail | where TimeGenerated > ago(Window + Baseline) | where EventSource == "bedrock.amazonaws.com" | extend Caller = coalesce(UserIdentityAccessKeyId, UserIdentityArn); let Known = Bedrock | where TimeGenerated <= ago(Window) | where EventName in (Invoke) | summarize KnownIPs = make_set(SourceIpAddress, 500) by Caller; Bedrock | where TimeGenerated > ago(Window) | summarize Invocations = countif(EventName in (Invoke)), InvocationIPs = make_set_if(SourceIpAddress, EventName in (Invoke), 50), ReconCalls = countif(EventName in (Recon)), UnlockCalls = countif(EventName in (Unlock)), LoggingDeletes = countif(EventName in (Blind)), BearerTokenCalls = countif(tostring(parse_json(AdditionalEventData).callWithBearerToken) =~ "true"), DeniedCalls = countif(isnotempty(ErrorCode)), Regions = make_set(AWSRegion, 10), UserAgents = make_set(UserAgent, 10), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by Caller, UserIdentityArn, RecipientAccountId | join kind=leftouter Known on Caller | extend NewInvocationIPs = set_difference(InvocationIPs, coalesce(KnownIPs, dynamic([]))) | where array_length(NewInvocationIPs) > 0 or UnlockCalls > 0 or LoggingDeletes > 0 | extend ReconAndInvoke = ReconCalls > 0 and Invocations > 0 | project-away Caller1, KnownIPs | order by LoggingDeletes desc, UnlockCalls desc, ReconAndInvoke desc, Invocations desc