Part 1 of this series established why AWS IAM security is the highest-risk surface area in most cloud environments: IAM debt accumulates gradually, it rarely triggers alarms, and it’s the most common path attackers exploit. Knowing that the problem exists is step one. Step two is knowing exactly what to look for — because the specific misconfiguration patterns are consistent and identifiable, once you know the signatures.
Here are the five AWS IAM misconfigurations we find most often, along with what they look like in practice and how to fix them.
Misconfiguration #1: Wildcard Actions on Broad Resources
High Risk
The wildcard IAM policy is the single most common finding in IAM security audits. It looks like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
}
]
}
Or worse, the full account wildcard:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}
What an attacker — or an accidental actor — can do with this: The s3:* on Resource: "*" variant gives any identity holding this policy unrestricted read, write, delete, and configuration access to every S3 bucket in the account. That includes application data, database backups, CloudTrail logs, and any other bucket that happens to exist. A compromised Lambda function with this policy can enumerate all buckets, exfiltrate their contents, delete objects, or overwrite data — with no restrictions at all. The full wildcard (Action: "*", Resource: "*") is effectively AdministratorAccess: the bearer can provision infrastructure, create new IAM roles, modify security groups, or do anything else the AWS APIs allow.
These policies exist because they work. When a developer is trying to ship quickly and can’t figure out the exact S3 actions needed, s3:* unblocks them immediately. The IAM security debt accumulates one shortcut at a time.
The fix — apply AWS least privilege:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::my-app-bucket/uploads/*"
}
]
}
Scope actions to only what the identity actually needs, and scope the resource to the specific bucket and prefix. If you’re not sure what actions a role actually uses, AWS IAM Access Analyzer and CloudTrail can help you derive the right scope from observed access patterns.
Misconfiguration #2: Overly Permissive Trust Policies
High Risk
IAM role trust policies define who can assume a role — but they’re easy to misconfigure in ways that open far broader access than intended. A common pattern looks like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "sts:AssumeRole"
}
]
}
Or a subtler but equally risky version:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::987654321098:root"
},
"Action": "sts:AssumeRole"
}
]
}
The cross-account risk: The first variant (Principal: "*") allows anyone — any AWS identity from any account, including accounts you have no relationship with — to attempt to assume this role. If the role’s permissions policy grants anything useful, this is a critical finding.
The second variant is a more common mistake: trusting the root of an external AWS account. When you trust arn:aws:iam::987654321098:root, you’re trusting every principal in that account — not just the specific service or user you intended. If any credential in that account is compromised, it can be used to assume your role. Role assumption chains extend this risk: once an attacker assumes the first role, they can potentially assume other roles from it.
The fix — scope trust to specific principals:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::987654321098:role/specific-deployment-role"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "your-unique-external-id"
}
}
}
]
}
Always trust a specific role ARN rather than an account root. For third-party integrations, use an ExternalId condition to prevent confused deputy attacks. Review cross-account trust relationships regularly — vendor relationships end, CI/CD pipelines change, and trust policies rarely get cleaned up automatically.
Misconfiguration #3: Unused Roles and Users with Active Credentials
Medium–High Risk
Not every IAM risk shows up in a policy JSON. Some of the most dangerous findings are identities that simply shouldn’t exist anymore.
The stale identity problem plays out in three common patterns:
Departed employees. An engineer leaves the company. Their IAM user account is disabled or their SSO access is revoked — but their long-lived programmatic access keys remain active. If those keys were stored in a personal laptop, a dotfile, or a local credentials file, they represent an ongoing credential exposure risk that persists long after the employee is gone.
Decommissioned services. A microservice is retired. Its IAM execution role stays in place, still attached to policies, still valid for assumption. If someone compromises a system that still holds references to that role, it may be assumable with permissions far broader than expected.
Old CI/CD integrations. A migration from Jenkins to GitHub Actions left behind an IAM user with programmatic credentials that were baked into the old pipeline configuration. The new pipeline doesn’t use them. The old credentials were never rotated or revoked.
Manual detection: AWS provides an IAM credential report (downloadable from the IAM console or via aws iam generate-credential-report) that shows last-used timestamps for all IAM users and their access keys. An access key that hasn’t been used in 90 days is a candidate for removal; one that hasn’t been used in 180+ days is a finding. For roles, IAM Access Advisor shows last-accessed timestamps per role per service.
The challenge is that this is a manual, point-in-time snapshot. Running the credential report tells you the current state — it doesn’t alert you when a key goes stale or when a role hasn’t been assumed in six months.
Misconfiguration #4: Inline Policies Hiding Permissions
Medium Risk
AWS IAM supports two types of policies: managed policies (standalone, reusable, visible in the IAM console’s policy list) and inline policies (embedded directly in a user, group, or role). Inline policies create a specific audit challenge.
When you list the managed policies attached to a role, you see all of them immediately. When you look at an IAM user with inline policies, the permissions only appear if you specifically inspect that user’s inline policy section. In a large account with dozens or hundreds of users and roles, inline policies are easy to miss during an IAM security audit.
Here’s what an inline policy looks like when attached directly to a user — this is the pattern to watch for:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:Describe*",
"ec2:StartInstances",
"ec2:StopInstances",
"ec2:TerminateInstances"
],
"Resource": "*"
}
]
}
There’s nothing inherently wrong with these permissions — but if this policy is inline on a user rather than a named managed policy, it won’t show up when you run aws iam list-policies. You’d need to call aws iam list-user-policies for each user, then aws iam get-user-policy for each result, to discover it. At scale, this becomes impractical.
The managed policy alternative:
{
"Version": "2012-10-17",
"Statement": [...]
}
Create this as a named managed policy — EC2InstanceOperatorPolicy, for example — and attach it to the role or user. Now it’s discoverable in the policy list, reusable across identities, and auditable without inspecting individual identities one by one.
As a general practice: prefer managed policies over inline policies. Inline policies should be the exception, not the default, and their presence in an account should itself be a flag for closer review.
Misconfiguration #5: Missing Permission Boundaries on Delegated Roles
Medium–High Risk
Permission boundaries are an underused IAM feature that prevent a specific class of privilege escalation. Here’s the scenario: you allow a developer or a service to create IAM roles. Without guardrails, they can create a role with any permissions they want — potentially permissions that exceed their own. This is privilege escalation by role creation.
Permission boundaries cap the maximum effective permissions of any role or user, regardless of what policies are attached. A boundary policy defines the ceiling:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*",
"dynamodb:*",
"logs:*"
],
"Resource": "*"
}
]
}
When this is set as the permission boundary on a delegated role, the role’s effective permissions are the intersection of its attached policies and the boundary — even if someone attaches AdministratorAccess to the role later, the boundary prevents it from exceeding the defined scope.
In environments where developers or automation can create roles (common in organizations that allow self-service infrastructure), missing permission boundaries mean that the role-creation capability itself becomes a privilege escalation path. A developer with iam:CreateRole and iam:AttachRolePolicy can grant themselves or their workloads permissions they don’t otherwise have. Permission boundaries close that gap.
The Audit Problem
Reading through these five patterns, a reasonable reaction is: “We can check for these manually.” And you can — with enough time. But the math doesn’t work in your favor.
Even a 20-person engineering organization running a non-trivial AWS workload will have accumulated dozens of IAM roles: execution roles for Lambda functions, instance profiles, deployment roles, cross-account roles for third-party integrations, developer IAM users, service accounts. Stack on managed policies, inline policies, resource-based policies on S3 buckets and SQS queues, and trust relationships across development, staging, and production accounts — and you’re looking at 100+ distinct IAM configurations to review.
A manual IAM security audit produces a point-in-time snapshot. You review your current state, document findings, and remediate what you can. Three months later, the environment has changed: new services deployed, new trust relationships created, old roles not yet cleaned up. The snapshot is already out of date. Without continuous visibility, you’re always working from stale data.
This is the gap between “we did an IAM review last quarter” and actually managing IAM risk as a continuous posture rather than a periodic exercise.
Detecting These Patterns Automatically
The five misconfigurations in this post — wildcard actions, overly permissive trust policies, stale identities, hidden inline policies, and missing permission boundaries — are detectable. They have signatures. An automated IAM security audit can identify all of them systematically, across every role and policy in your account, in minutes.
That’s exactly what Horizon IAM Risk Analyzer is built to do. It connects to your AWS environment, maps your IAM posture, and surfaces these specific overly permissive IAM policy patterns with AI-powered remediation guidance — including the corrected policy JSON you need to fix each finding.
Stop Guessing What's in Your IAM
Horizon IAM Risk Analyzer detects wildcard policies, overly broad trust relationships, stale credentials, hidden inline policies, and missing permission boundaries — automatically, with remediation guidance built in.
[Start your free 14-day trial on AWS Marketplace →]Up next — Part 3: Privilege Escalation Attack Paths in AWS IAM. Once an attacker has a foothold in your environment, specific IAM configurations allow them to escalate from limited access to full account control without ever touching a vulnerability. We’ll walk through the actual techniques and show you what the policies that enable them look like.