Back to Checks
No potential LLM Jacking attacks detected across all Azure API Management instances
apim_threat_detection_llm_jacking
Service: apimSeverity: critical
OfficialBuilt-inVerified
Categories:gen-ailoggingthreat-detection
Resource Type:microsoft.apimanagement/service
API Management diagnostic logs in Log Analytics are analyzed for LLM-related operations. Requests are grouped by caller IP, the number of distinct monitored actions (e.g., ChatCompletions_Create, ImageGenerations_Create) within a configurable minutes window is measured, and that ratio is compared to a threshold to surface anomalous multi-action patterns.
Risk
Concentrated LLM activity from one IP indicates automation or leaked credentials.
- Availability/cost: rapid token burn and quota exhaustion
- Confidentiality: exposure of prompts/completions and model details
- Integrity: abuse of deployment/model actions enabling unauthorized changes or mass content generation
Run this check with Prowler
Run in Prowler CloudRun
prowler azure --checks apim_threat_detection_llm_jackingRecommendation
Adopt defense in depth for LLM APIs:
- Enforce least privilege; isolate management from inference
- Prefer managed identities over keys; rotate secrets
- Apply quotas, rate limiting, and IP allowlisting; use private access
- Alert on anomalous action diversity; review logs
Tune threshold and minutes for your environment.
Remediation
Terraform
# Global APIM policy that blocks a specific IP
resource "azurerm_api_management_policy" "<example_resource_name>" {
api_management_id = "<example_resource_id>"
# Critical: Policy XML that blocks the offending IP by returning 403
xml_content = <<XML
<policies>
<inbound>
<base />
<choose>
<!-- Critical: Block offending IP to stop LLM jacking -->
<when condition="@(context.Request.IpAddress == \"<OFFENDING_IP>\")">
<return-response>
<set-status code="403" reason="Forbidden" />
</return-response>
</when>
</choose>
</inbound>
<backend><base /></backend>
<outbound><base /></outbound>
<on-error><base /></on-error>
</policies>
XML
}NativeIaC
// Blocks a specific IP at the global (service) policy level for APIM
param apimName string
param blockedIp string
resource apim 'Microsoft.ApiManagement/service@2023-05-01-preview' existing = {
name: apimName
}
resource apimPolicy 'Microsoft.ApiManagement/service/policies@2023-05-01-preview' = {
parent: apim
name: 'policy'
properties: {
value: '<policies><inbound><base /><choose><!-- Critical: Block offending IP to stop LLM jacking -->
<when condition="@(context.Request.IpAddress == "${blockedIp}")">
<return-response><set-status code="403" reason="Forbidden" /></return-response>
</when>
</choose></inbound><backend><base /></backend><outbound><base /></outbound><on-error><base /></on-error></policies>' // Critical: Policy XML that blocks the offending IP
format: 'xml' // Critical: Apply policy as XML
}
}Other
1In the Azure portal, open your API Management instance
2Go to APIs > All APIs
3Click Policies (Inbound processing)
4Add a when block to block the offending IP:
5- Condition: @(context.Request.IpAddress == "<OFFENDING_IP>")
6- Action: return-response with status 403 Forbidden
7Save the policy
8Re-run the scan after the detection window elapses to confirm PASS
Check Code
from typing import List
from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.apim.apim_client import apim_client
from prowler.providers.azure.services.apim.apim_service import LogsQueryLogEntry
class apim_threat_detection_llm_jacking(Check):
def execute(self):
findings = []
# Get configuration from audit config with defaults
threshold = float(
getattr(apim_client, "audit_config", {}).get(
"apim_threat_detection_llm_jacking_threshold", 0.1
)
)
threat_detection_minutes = getattr(apim_client, "audit_config", {}).get(
"apim_threat_detection_llm_jacking_minutes", 1440
)
monitored_actions = getattr(apim_client, "audit_config", {}).get(
"apim_threat_detection_llm_jacking_actions",
[
# OpenAI API endpoints
"ImageGenerations_Create",
"ChatCompletions_Create",
"Completions_Create",
"Embeddings_Create",
"FineTuning_Jobs_Create",
"Models_List",
# Azure OpenAI endpoints
"Deployments_List",
"Deployments_Get",
"Deployments_Create",
"Deployments_Delete",
# Anthropic endpoints
"Messages_Create",
"Claude_Create",
# Google AI endpoints
"GenerateContent",
"GenerateText",
"GenerateImage",
# Meta AI endpoints
"Llama_Create",
"CodeLlama_Create",
# Other LLM endpoints
"Gemini_Generate",
"Claude_Generate",
"Llama_Generate",
],
)
for subscription, instances in apim_client.instances.items():
subscription_name = apim_client.subscriptions.get(
subscription, subscription
)
all_llm_logs: List[LogsQueryLogEntry] = []
for instance in instances:
if instance.log_analytics_workspace_id:
logs = apim_client.get_llm_operations_logs(
subscription, instance, threat_detection_minutes
)
all_llm_logs.extend(logs)
# Analyze logs only within the current subscription to avoid
# cross-subscription attribution when scanning multiple subscriptions.
potential_llm_jacking_attackers = {}
for log in all_llm_logs:
operation_name = log.operation_id
caller_ip = log.caller_ip_address
if operation_name in monitored_actions and caller_ip:
# Use IP address as the principal identifier
if caller_ip not in potential_llm_jacking_attackers:
potential_llm_jacking_attackers[caller_ip] = set()
potential_llm_jacking_attackers[caller_ip].add(operation_name)
# 3. Check each principal against the threshold and report failures
found_potential_llm_jacking_attackers = False
for (
principal_ip,
distinct_actions,
) in potential_llm_jacking_attackers.items():
action_ratio = round(len(distinct_actions) / len(monitored_actions), 2)
if action_ratio > threshold:
found_potential_llm_jacking_attackers = True
# Build Identity resource for the report
resource = {
"name": principal_ip,
"id": principal_ip,
}
# Report against the subscription, identifying the offending principal (IP)
report = Check_Report_Azure(self.metadata(), resource=resource)
report.subscription = subscription
report.status = "FAIL"
report.status_extended = f"Potential LLM Jacking attack detected from IP address {principal_ip} in subscription {subscription_name} ({subscription}) with an action ratio of {action_ratio}, above the configured threshold of {threshold}."
findings.append(report)
# If no threats were found after checking all principals, create a single PASS report.
if not found_potential_llm_jacking_attackers:
report = Check_Report_Azure(self.metadata(), resource={})
report.resource_name = subscription_name
report.resource_id = f"/subscriptions/{subscription}"
report.subscription = subscription
report.status = "PASS"
report.status_extended = f"No potential LLM Jacking attacks detected across monitored APIM instances in subscription {subscription_name} ({subscription}) in the last {threat_detection_minutes} minutes."
findings.append(report)
return findings