IAM Access Analyzer: Verify AI-Generated Policies Before Attach

IAM Access Analyzer: Verify AI-Generated Policies Before Attach

AI coding agents invent IAM JSON with the confidence of a junior who discovered ChatGPT and the blast radius of a senior who forgot to open the simulator. Wildcards, missing Condition blocks, and Resource: "*" on s3:* show up dressed as “least privilege.” Access Analyzer plus the policy simulator are the gates—not vibes.

⚡ TL;DR: Treat model-emitted IAM as untrusted input. Run Access Analyzer (policy validation + external access findings), simulate every statement against deny/allow fixtures, and only then attach via CI. Reject * Actions/Resources unless an allowlisted exception ticket exists. Pair with Cursor Rules for AWS CDK and AI-Written Terraform Plan Diff Gates.

Never attach raw model JSON

// scripts/iam-ai-gate.ts
import { IAMClient, SimulatePrincipalPolicyCommand } from "@aws-sdk/client-iam";
import { AccessAnalyzerClient, ValidatePolicyCommand } from "@aws-sdk/client-accessanalyzer";

export type AiPolicyDraft = {
  policyDocument: string;
  roleName: string;
  ticket: string;
};

const FORBIDDEN = [
  /"Action"\s*:\s*"\*"/,
  /"Action"\s*:\s*\[["']\*["']\]/,
  /"Resource"\s*:\s*"\*"/,
  /"NotAction"/,
];

export function assertNoWildcards(doc: string) {
  for (const re of FORBIDDEN) {
    if (re.test(doc)) throw new Error(`forbidden_iam_pattern:${re}`);
  }
}

export async function validateWithAnalyzer(doc: string) {
  const aa = new AccessAnalyzerClient({});
  const out = await aa.send(new ValidatePolicyCommand({
    policyDocument: doc,
    policyType: "IDENTITY_POLICY",
  }));
  const errors = (out.findings ?? []).filter((f) => f.findingType === "ERROR");
  if (errors.length) throw new Error(JSON.stringify(errors));
  return out.findings ?? [];
}

✅ Analyzer + regex denylist before any PutRolePolicy.
aws iam put-role-policy from an agent shell with no simulation.

Simulate the happy path and the deny path

const iam = new IAMClient({});

export async function simulateMustAllow(roleArn: string, actions: string[], resources: string[]) {
  const r = await iam.send(new SimulatePrincipalPolicyCommand({
    PolicySourceArn: roleArn,
    ActionNames: actions,
    ResourceArns: resources,
  }));
  for (const e of r.EvaluationResults ?? []) {
    if (e.EvalDecision !== "allowed") {
      throw new Error(`expected_allow:${e.EvalActionName}:${e.EvalDecision}`);
    }
  }
}

export async function simulateMustDeny(roleArn: string, actions: string[], resources: string[]) {
  const r = await iam.send(new SimulatePrincipalPolicyCommand({
    PolicySourceArn: roleArn,
    ActionNames: actions,
    ResourceArns: resources,
  }));
  for (const e of r.EvaluationResults ?? []) {
    if (e.EvalDecision === "allowed") {
      throw new Error(`expected_deny_got_allow:${e.EvalActionName}`);
    }
  }
}

// Fixture: agent proposed s3:GetObject on payments bucket — must allow
// Same role must DENY s3:DeleteBucket on org-security bucket

Encode fixtures next to the policy in the PR so reviewers see intent, not only JSON. For CDK-generated policies from agents, see Secure AI Sandboxes.

CI gate shape

# .github/workflows/iam-ai-gate.yml
name: iam-ai-gate
on:
  pull_request:
    paths: ["infra/iam/**", "**/iam-policies/**"]
jobs:
  analyze:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pnpm tsx scripts/iam-ai-gate.ts --dir infra/iam
      - name: Fail on Access Analyzer ERROR findings
        run: test ! -s analyzer-errors.json

Feedback loop into the generator

Feed Analyzer findings back into the next model prompt as structured negatives—same spirit as banned-API allowlists in Agent Tool Allowlists—paste Analyzer findings into the agent system prompt as hard bans.

DENIED_PATTERNS:
- Action "*"
- Resource "*" on iam:*, kms:*, s3:*
- Missing aws:PrincipalOrgID on cross-account trusts

Closing checklist

  • [ ] Access Analyzer ValidatePolicy returns zero ERROR findings
  • [ ] Policy simulator has allow fixtures and deny fixtures
  • [ ] Wildcards blocked unless exception ticket referenced in PR body
  • [ ] Attach only from CI role, never from laptop long-lived keys
  • [ ] CloudTrail alarm on PutRolePolicy / AttachRolePolicy outside the pipeline
  • [ ] Agent tool that mutates IAM requires human dual-control (see AI On-Call Copilots)

Related reading

Last updated on September 11, 2026


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply