Back to Checks
No potential LLM jacking activity detected in CloudTrail
cloudtrail_threat_detection_llm_jacking
Service: cloudtrailSeverity: critical
OfficialBuilt-inVerified
Categories:threat-detectiongen-ai
Resource Type:AwsCloudTrailTrail
CloudTrail Bedrock activity is analyzed per identity for a high diversity of LLM-related API calls (e.g., InvokeModel, InvokeModelWithResponseStream, GetFoundationModelAvailability). If an identity's share of these actions exceeds a configured threshold over a recent window, it is surfaced as potential LLM-jacking behavior.
Risk
Such patterns suggest stolen credential abuse to drive LLM usage.
- Availability: cost exhaustion and service disruption
- Confidentiality: leakage of prompts/outputs and model settings
- Integrity: misuse of permissions for broader access Attackers may use reverse proxies to resell access and obfuscate sources.
Run this check with Prowler
Run in Prowler CloudRun
prowler aws --checks cloudtrail_threat_detection_llm_jackingRecommendation
Apply least privilege to Bedrock; restrict Invoke* only to required roles and deny broadly via SCPs where unused. Enforce MFA and short-lived creds; rotate/remove exposed keys. Enable model invocation logging and budgets/quotas. Continuously monitor for Bedrock enumeration plus invoke bursts. Use defense in depth across identities and networks.
Remediation
Terraform
# SCP denying all Amazon Bedrock actions; attach it to the root/OU/account to halt LLM jacking
resource "aws_organizations_policy" "main" {
name = "<example_resource_name>"
type = "SERVICE_CONTROL_POLICY"
content = jsonencode({
Version = "2012-10-17"
Statement = [{
Sid = "DenyBedrock"
Effect = "Deny"
Action = "bedrock:*" // CRITICAL: blocks all Bedrock APIs (prevents further suspicious activity)
Resource = "*" // CRITICAL: deny across all resources
}]
})
}
resource "aws_organizations_policy_attachment" "attach" {
policy_id = aws_organizations_policy.main.id
target_id = "<example_resource_id>" // CRITICAL: attach to the affected account/OU/root to enforce the deny
}NativeIaC
# CloudFormation SCP that blocks all Amazon Bedrock actions to stop LLM jacking
Resources:
<example_resource_name>:
Type: AWS::Organizations::Policy
Properties:
Name: <example_resource_name>
Type: SERVICE_CONTROL_POLICY
TargetIds:
- "<example_resource_id>" # CRITICAL: Attach SCP to the root/OU/account to enforce the deny
Content:
Version: "2012-10-17"
Statement:
- Sid: DenyBedrock
Effect: Deny
Action: "bedrock:*" # CRITICAL: Denies all Bedrock APIs (Invoke/Converse/list/entitlements/etc.)
Resource: "*" # CRITICAL: Apply deny to all resourcesOther
1In the AWS Console, go to Organizations > Policies > Service control policies
2Click Create policy
3Set Name to <example_resource_name>
4In Policy, paste a deny for Bedrock:
5{
6"Version": "2012-10-17",
7"Statement": [{"Sid":"DenyBedrock","Effect":"Deny","Action":"bedrock:*","Resource":"*"}]
8}
9Save the policy and click Attach
10Select the target (Root, OU, or the affected account ID <example_resource_id>) and attach the policy
11Wait for propagation; no further Bedrock calls will occur, and the finding will clear after the detection window elapses
Check Code
import json
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.cloudtrail.cloudtrail_client import (
cloudtrail_client,
)
default_threat_detection_llm_jacking_actions = [
"PutUseCaseForModelAccess",
"PutFoundationModelEntitlement",
"PutModelInvocationLoggingConfiguration",
"CreateFoundationModelAgreement",
"InvokeModel",
"InvokeModelWithResponseStream",
"GetUseCaseForModelAccess",
"GetModelInvocationLoggingConfiguration",
"GetFoundationModelAvailability",
"ListFoundationModelAgreementOffers",
"ListFoundationModels",
"ListProvisionedModelThroughputs",
"SearchAgreements",
"AcceptAgreementRequest",
]
class cloudtrail_threat_detection_llm_jacking(Check):
def execute(self):
findings = []
threshold = cloudtrail_client.audit_config.get(
"threat_detection_llm_jacking_threshold", 0.4
)
threat_detection_minutes = cloudtrail_client.audit_config.get(
"threat_detection_llm_jacking_minutes", 1440
)
llm_jacking_actions = cloudtrail_client.audit_config.get(
"threat_detection_llm_jacking_actions",
default_threat_detection_llm_jacking_actions,
)
potential_llm_jacking = {}
found_potential_llm_jacking = False
multiregion_trail = None
# Check if any trail is multi-region so we only need to check once
for trail in cloudtrail_client.trails.values():
if trail.is_multiregion:
multiregion_trail = trail
break
trails_to_scan = (
cloudtrail_client.trails.values()
if not multiregion_trail
else [multiregion_trail]
)
for trail in trails_to_scan:
for event_name in llm_jacking_actions:
for event_log in cloudtrail_client._lookup_events(
trail=trail,
event_name=event_name,
minutes=threat_detection_minutes,
):
event_log = json.loads(event_log["CloudTrailEvent"])
if (
"arn" in event_log["userIdentity"]
): # Ignore event logs without ARN since they are AWS services
if (
event_log["userIdentity"]["arn"],
event_log["userIdentity"]["type"],
) not in potential_llm_jacking:
potential_llm_jacking[
(
event_log["userIdentity"]["arn"],
event_log["userIdentity"]["type"],
)
] = set()
potential_llm_jacking[
(
event_log["userIdentity"]["arn"],
event_log["userIdentity"]["type"],
)
].add(event_name)
for aws_identity, actions in potential_llm_jacking.items():
identity_threshold = round(len(actions) / len(llm_jacking_actions), 2)
aws_identity_type = aws_identity[1]
aws_identity_arn = aws_identity[0]
if len(actions) / len(llm_jacking_actions) > threshold:
found_potential_llm_jacking = True
report = Check_Report_AWS(
metadata=self.metadata(), resource=cloudtrail_client.trails
)
report.region = cloudtrail_client.region
report.resource_id = aws_identity_arn.split("/")[-1]
report.resource_arn = aws_identity_arn
report.status = "FAIL"
report.status_extended = f"Potential LLM Jacking attack detected from AWS {aws_identity_type} {aws_identity_arn.split('/')[-1]} with a threshold of {identity_threshold}."
findings.append(report)
if not found_potential_llm_jacking:
report = Check_Report_AWS(
metadata=self.metadata(), resource=cloudtrail_client.trails
)
report.region = cloudtrail_client.region
report.resource_id = cloudtrail_client.audited_account
report.resource_arn = cloudtrail_client._get_trail_arn_template(
cloudtrail_client.region
)
report.status = "PASS"
report.status_extended = "No potential LLM Jacking attack detected."
findings.append(report)
return findings