Amazon Bedrock agent execution role follows least privilege
Bedrock Agent execution roles (agentResourceRoleArn) should grant only the minimum permissions the agent needs. The evaluation FAILs when the role has an AWS-managed *FullAccess policy attached, has an inline statement allowing broad actions on Resource: "*", or has no permissions boundary configured.
Risk
An overly permissive Bedrock Agent execution role turns a successful prompt injection into AWS privilege escalation. A model coerced into calling tools can invoke any API the role allows — reading secrets, modifying IAM, exfiltrating data from S3, or pivoting laterally. Least privilege plus a permissions boundary keeps the blast radius bounded even when guardrails fail.
Run this check with Prowler
Run in Prowler Cloudprowler aws --checks bedrock_agent_role_least_privilegeRecommendation
Apply least privilege to every Bedrock Agent execution role: scope Action and Resource to exactly what the agent needs, avoid AWS-managed *FullAccess policies, and always attach a permissions boundary so that future policy edits cannot exceed an approved ceiling. Treat agent roles as high-risk because prompt injection can weaponize any granted permission.
Remediation
CLI
aws iam put-role-permissions-boundary --role-name <execution_role_name> --permissions-boundary <boundary_policy_arn>Terraform
resource "aws_iam_role" "bedrock_agent" {
name = "<execution_role_name>"
assume_role_policy = data.aws_iam_policy_document.trust.json
permissions_boundary = aws_iam_policy.bedrock_agent_boundary.arn # CRITICAL: caps maximum privileges
}
resource "aws_iam_role_policy" "bedrock_agent_inline" {
role = aws_iam_role.bedrock_agent.name
policy = jsonencode({
Version = "2012-10-17",
Statement = [{
Effect = "Allow",
Action = ["s3:GetObject"], # CRITICAL: narrow action
Resource = ["arn:aws:s3:::my-rag-bucket/*"] # CRITICAL: narrow resource
}]
})
}Other
Check Code
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.bedrock.bedrock_agent_client import (
bedrock_agent_client,
)
from prowler.providers.aws.services.iam.iam_client import iam_client
from prowler.providers.aws.services.iam.lib.policy import check_admin_access
from prowler.providers.aws.services.iam.lib.privilege_escalation import (
check_privilege_escalation,
)
class bedrock_agent_role_least_privilege(Check):
"""Ensure Bedrock Agent execution roles follow least privilege.
A Bedrock Agent's execution role is evaluated against three criteria:
- No AWS-managed ``*FullAccess`` policy attached.
- No attached or inline policy granting administrative access or known
privilege escalation combinations.
- A permissions boundary is configured on the role.
"""
def execute(self) -> list[Check_Report_AWS]:
"""Run the least-privilege evaluation across all Bedrock Agents.
Returns:
A list of ``Check_Report_AWS`` with one entry per agent. The
status is ``FAIL`` when any of the criteria above is violated and
``MANUAL`` when the execution role cannot be resolved in IAM. When
the IAM role inventory itself could not be listed, a single
account-level ``MANUAL`` report is returned instead.
"""
findings = []
if iam_client.roles is None and bedrock_agent_client.agents:
# iam:ListRoles was denied: this is an account-wide condition, so
# emit one account-level MANUAL instead of one per agent.
report = Check_Report_AWS(metadata=self.metadata(), resource={})
report.region = iam_client.region
report.resource_id = iam_client.audited_account
report.resource_arn = iam_client.audited_account_arn
report.status = "MANUAL"
report.status_extended = "Cannot evaluate Bedrock Agent execution roles: the IAM roles could not be listed. Verify that the scanning credentials are allowed to call iam:ListRoles."
findings.append(report)
return findings
roles_by_arn = {role.arn: role for role in (iam_client.roles or [])}
for agent in bedrock_agent_client.agents.values():
report = Check_Report_AWS(metadata=self.metadata(), resource=agent)
report.status = "PASS"
report.status_extended = (
f"Bedrock Agent {agent.name} execution role follows least privilege."
)
role = roles_by_arn.get(agent.role_arn) if agent.role_arn else None
if role is None:
report.status = "MANUAL"
report.status_extended = (
f"Bedrock Agent {agent.name} execution role could not be "
f"resolved in IAM and cannot be evaluated for least privilege; "
f"verify the role manually."
)
findings.append(report)
continue
violations = []
for policy in role.attached_policies:
policy_arn = policy.get("PolicyArn", "")
policy_name = policy.get("PolicyName") or policy_arn
if policy_arn.startswith(
"arn:aws:iam::aws:policy/"
) and policy_arn.endswith("FullAccess"):
violations.append(
f"managed policy {policy_name} grants full access"
)
continue
policy_obj = iam_client.policies.get(policy_arn)
if policy_obj is None or not policy_obj.document:
continue
document = policy_obj.document
if check_admin_access(document):
violations.append(
f"managed policy {policy_name} grants administrative access"
)
elif check_privilege_escalation(document):
violations.append(
f"managed policy {policy_name} allows privilege escalation"
)
for inline_name in role.inline_policies:
policy_obj = iam_client.policies.get(f"{role.arn}:policy/{inline_name}")
if policy_obj is None or not policy_obj.document:
continue
document = policy_obj.document
if check_admin_access(document):
violations.append(
f"inline policy {inline_name} grants administrative access"
)
elif check_privilege_escalation(document):
violations.append(
f"inline policy {inline_name} allows privilege escalation"
)
if not role.permissions_boundary:
violations.append("no permissions boundary configured")
if violations:
report.status = "FAIL"
report.status_extended = (
f"Bedrock Agent {agent.name} execution role violates least "
f"privilege: {'; '.join(violations)}."
)
findings.append(report)
return findings