← Alle Neuigkeiten
configuration5 Min. Lesezeit

Wildcard IAM policies: how AI-generated Terraform grants more than it meant to

Von Seaworthy · 5. August 2026

3D render of a cloud computing concept

Photo by Growtika on Unsplash

Ask an AI coding assistant for "an IAM policy so this Lambda can read uploaded files from S3," and there's a good chance you get something that works on the first try and grants far more than a Lambda that reads uploaded files needs:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:*",
      "Resource": "*"
    }
  ]
}

It compiles. It deploys. The function stops throwing AccessDenied. Every box that matters to shipping the feature gets checked, and the policy that made that happen now has full read, write, and delete access to every S3 bucket in the AWS account, not the one bucket the function was actually written to touch.

How the policy ends up this wide

Nobody sets out to grant * on *. It's the endpoint of an iteration loop that optimizes for exactly one signal: whether the error is gone.

Writing a correctly-scoped IAM policy requires knowing, upfront, the exact set of actions a piece of code needs (s3:GetObject, not s3:*) and the exact resource ARNs it should be allowed to touch (arn:aws:s3:::uploads-bucket/*, not *). That's information the code's author has and the error message doesn't. When an AI assistant, or a developer moving fast, is handed only the AccessDenied exception and told to fix it, the only feedback loop available is "does the error still happen." A wildcard resolves it in one shot, every time, regardless of what the actual requirement was. A correctly scoped policy might take two or three iterations to get exactly right. Under any time pressure at all, the loop stops at the first version that works, and the first version that works is usually the widest one.

This is a specific instance of a broader pattern: security boundaries that require information the AI assistant wasn't given (which S3 objects should this specific function be able to touch, and only those) get replaced with boundaries that require no information at all (everything, always).

Why the blast radius is bigger than it looks

A wildcard policy attached to one function doesn't just mean that function is over-permissioned in the abstract. It means the security posture of your entire S3 estate is now equal to the security posture of that one Lambda. If that function has any vulnerability at all, an injection bug, a dependency with a known CVE, an exposed debug endpoint, anything that lets an attacker get code execution or forge a request in that function's execution context, the attacker doesn't inherit "read access to the uploads bucket." They inherit full read, write, and delete access to every bucket in the account: other services' data, backups, Terraform state files, anything else stored in S3 anywhere in that AWS account, including buckets that have nothing to do with the function that got compromised.

This compounds specifically in AI-generated infrastructure code because Terraform and CloudFormation modules get copied, adapted, and reused across a project quickly. A wildcard policy pattern that solved one AccessDenied error for one function tends to get pasted as the starting point for the next function's policy too, because it's known to work. What started as scope creep on a single Lambda becomes the house style for every IAM policy in the repo.

The fix

Scope both dimensions explicitly: the actions to exactly what the code calls, the resource to exactly the ARN(s) it should touch.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject"],
      "Resource": "arn:aws:s3:::uploads-bucket/*"
    }
  ]
}

In Terraform, the same scoping applies to the resource definition, and it's worth writing the policy against the actual bucket resource rather than a hardcoded ARN string, so a bucket rename doesn't silently leave a stale wildcard-shaped gap:

data "aws_iam_policy_document" "lambda_s3_access" {
  statement {
    effect    = "Allow"
    actions   = ["s3:GetObject", "s3:PutObject"]
    resources = ["${aws_s3_bucket.uploads.arn}/*"]
  }
}

resource "aws_iam_role_policy" "lambda_s3" {
  role   = aws_iam_role.lambda_exec.id
  policy = data.aws_iam_policy_document.lambda_s3_access.json
}

If you inherited a policy you didn't write and aren't sure how wide it actually is, aws iam simulate-principal-policy will tell you exactly what a role can do against a specific resource without needing to trigger the action for real, which is a faster way to audit existing policies than reading raw JSON and guessing. AWS Access Analyzer is worth turning on for the same reason, it flags resources with policies that grant access beyond what's actually used, which catches exactly this class of over-broad grant after the fact.

The underlying discipline doesn't change across cloud providers: start from the specific actions the code calls and the specific resources it touches, not from "grant everything, then it'll definitely work."

Seaworthy's configuration checks flag wildcard Action and Resource entries in IAM policy documents and Terraform aws_iam_policy / aws_iam_policy_document resources before they're ever applied, so the scope of a grant gets reviewed at write time instead of discovered during an incident.

Dieser Artikel wurde von KI generiert und fasst öffentlich verfügbare Quellen zusammen.

Von den Machern von Seaworthy

Seaworthy scannt dein Repository nach den Problemen, die in Artikeln wie diesem behandelt werden.

Sicherheitslücken, offengelegte Geheimnisse und Fehlkonfigurationen — erkannt, bevor du bereitstellst. Kostenlos, kein Konto nötig.

$ npx seaworthycode