LoFP LoFP / automated agents, chat applications, retrieval-augmented generation services, evaluation pipelines, and load tests routinely generate high bedrock inference volume against one model and will exceed any fixed threshold. validate the principal, user agent, source ip, and application context before treating the activity as malicious, and tune the threshold to the deployment.

Techniques

Sample rules

AWS Bedrock High-Frequency Single-Model Inference API Probing

Description

Identifies an AWS principal performing a high volume of Amazon Bedrock inference API calls against a single model within a short window. Membership inference attacks require hundreds to thousands of statistically similar queries whose prompts and responses are intentionally content-benign, making guardrail- and content-based rules ineffective. This rule detects the high-frequency single-model probing pattern that precedes membership inference and related exfiltration via the inference API. It is a behavioral / volumetric precursor: it does not observe model confidence scores and a fixed call-count threshold only catches the loud variant, so paced, low-and-slow, or credential-distributed probing will evade it. Definitive membership inference detection requires ML anomaly analysis over per-entity inference-rate and response-distribution baselines.

Detection logic

from logs-aws.cloudtrail-*
 
// Bedrock runtime inference APIs (CloudTrail management events, logged by default) used to probe at scale
| where
  event.provider == "bedrock.amazonaws.com"
  and event.action in (
    "InvokeModel",
    "Converse",
    "ConverseStream",
    "InvokeModelWithResponseStream"
  )
  and event.outcome == "success"
  and aws.cloudtrail.user_identity.arn IS NOT NULL
  and aws.cloudtrail.request_parameters IS NOT NULL
 
| grok aws.cloudtrail.request_parameters """modelId=(?<Esql.model_id>[^,}\]]+)"""
| where Esql.model_id IS NOT NULL
 
// preserve the grouping keys plus the ECS context fields collected via VALUES() below
| keep
  aws.cloudtrail.user_identity.arn,
  cloud.account.id,
  Esql.model_id,
  event.action,
  source.ip,
  user_agent.original,
  aws.cloudtrail.user_identity.type,
  aws.cloudtrail.user_identity.access_key_id,
  cloud.region,
  source.as.organization.name
 
// aggregate per principal + account + model, capturing analyst context with VALUES()
| stats
    Esql.inference_call_count = count(*),
    Esql.event_action_values = VALUES(event.action),
    Esql.source_ip_values = VALUES(source.ip),
    Esql.user_agent_original_values = VALUES(user_agent.original),
    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.source_as_organization_name_values = VALUES(source.as.organization.name)
  by
    aws.cloudtrail.user_identity.arn,
    cloud.account.id,
    Esql.model_id
 
| where Esql.inference_call_count >= 500
 
| keep
  aws.cloudtrail.user_identity.arn,
  cloud.account.id,
  Esql.model_id,
  Esql.inference_call_count,
  Esql.event_action_values,
  Esql.source_ip_values,
  Esql.user_agent_original_values,
  Esql.aws_cloudtrail_user_identity_type_values,
  Esql.aws_cloudtrail_user_identity_access_key_id_values,
  Esql.cloud_region_values,
  Esql.source_as_organization_name_values
 
| sort Esql.inference_call_count desc