Background Cursor agents will open a PR while you sleep. That is leverage until the diff invents iam.ManagedPolicy.fromAwsManagedPolicyName("AdministratorAccess"), a nonexistent L2 construct, or an S3 bucket policy that trusts *. Reviewers cannot outread agents at volume. The unfair advantage is a cheap, mandatory CI gauntlet tuned for AI-shaped AWS mistakes: synth, policy lint, scoped terraform/CDK plan, and path-based owners — before merge, not after the incident.
⚡ TL;DR: Treat agent PRs as untrusted input. Require
cdk synth/tscfor touched packages, run IAM policy lint (no*onAction/Resourcein app roles), block unknown CloudFormation resource types, and require human approval forinfra/**plus identity policies. Encode the same rules in Cursor rules for AWS CDK so the agent fails locally first. Pair with Cursor rules for TS monorepos and AI code review bots.
Threat model: what agents invent
Typical hallucinated AWS changes:
- Wildcard IAM (
Action: "*",Resource: "*") on Lambda/task roles - Fake CDK APIs (
bucket.grantMagic(...)) - Hard-coded account ids / regions from training data
- Security group
0.0.0.0/0“for debugging” - New AWS managed policies attached “to unblock”
cdk.Contextor secrets inlined into source
// ci/detect-iam-wildcards.ts — fail the job on app-role wildcards
import { readFileSync, readdirSync, statSync } from "node:fs";
import { join } from "node:path";
const BAD = [
/actions?\s*:\s*\[\s*["']\*["']\s*\]/i,
/resources?\s*:\s*\[\s*["']\*["']\s*\]/i,
/ManagedPolicyName\(["']AdministratorAccess["']\)/,
/aws_iam_policy_document[\s\S]{0,200}actions\s*=\s*\["\*"/i,
];
export function scan(dir: string): string[] {
const hits: string[] = [];
for (const name of readdirSync(dir)) {
const p = join(dir, name);
if (statSync(p).isDirectory()) {
if (name === "node_modules" || name === "cdk.out") continue;
hits.push(...scan(p));
continue;
}
if (!/\.(ts|js|json|yaml|yml|tf)$/.test(name)) continue;
const text = readFileSync(p, "utf8");
for (const re of BAD) {
if (re.test(text)) hits.push(`${p} :: ${re}`);
}
}
return hits;
}
const hits = scan(process.argv[2] ?? "infra");
if (hits.length) {
console.error("iam_wildcard_hits\n" + hits.join("\n"));
// ❌ Allowing merge with “we will tighten later”
process.exit(1);
}
Gate 1: package-local typecheck + cdk synth
Agents love cross-package edits that typecheck in the IDE cache and fail in CI. Mirror monorepo agent rules: affected-only turbo tasks, plus synth for any PR touching infra/.
# .github/workflows/agent-pr-gates.yml (illustrative)
name: agent-pr-gates
on:
pull_request:
paths:
- "**"
jobs:
cheap-gates:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Detect infra touch
id: infra
run: |
if git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -E '^(infra|cdk|terraform)/'; then
echo "touched=true" >> $GITHUB_OUTPUT
else
echo "touched=false" >> $GITHUB_OUTPUT
fi
- name: Typecheck affected
run: pnpm turbo run typecheck --filter=...[origin/${{ github.base_ref }}]
- name: IAM wildcard scan
run: pnpm tsx ci/detect-iam-wildcards.ts infra
- name: CDK synth
if: steps.infra.outputs.touched == 'true'
run: pnpm --filter @acme/infra exec cdk synth --strict
env:
# ✅ Fake context — never real credentials in PR CI for deploy
CDK_DEFAULT_ACCOUNT: "123456789012"
CDK_DEFAULT_REGION: "us-east-1"
cdk synth --strict catches many invented props before a human opens the files. It will not catch semantic IAM overbreadth — that is why the regex/AST lint stays.
Gate 2: policy lint with allowlisted exceptions
Move beyond regex when you can: parse CloudFormation from cdk.out and flag Effect: Allow statements where Action or Resource is *, unless the statement Sid is in a reviewed allowlist (e.g. CloudWatch PutMetricData on *).
// ci/lint-cfn-iam.ts
import { readFileSync } from "node:fs";
type Statement = {
Sid?: string;
Effect?: string;
Action?: string | string[];
Resource?: string | string[];
};
const ALLOWLIST_SIDS = new Set(["CloudWatchPutMetrics", "XRayWrite"]);
export function lintTemplate(path: string) {
const tpl = JSON.parse(readFileSync(path, "utf8"));
const errors: string[] = [];
const resources = tpl.Resources ?? {};
for (const [logicalId, res] of Object.entries(resources) as [string, any][]) {
if (res.Type !== "AWS::IAM::Policy" && res.Type !== "AWS::IAM::ManagedPolicy") continue;
const stmts: Statement[] = res.Properties?.PolicyDocument?.Statement ?? [];
for (const s of stmts) {
if (s.Effect !== "Allow") continue;
if (s.Sid && ALLOWLIST_SIDS.has(s.Sid)) continue;
const actions = Arr(s.Action);
const resourcesArr = Arr(s.Resource);
if (actions.includes("*") || resourcesArr.includes("*")) {
errors.push(`${logicalId} sid=${s.Sid ?? "?"} wildcard`);
}
}
}
return errors;
}
function Arr(v?: string | string[]) {
return v == null ? [] : Array.isArray(v) ? v : [v];
}
Gate 3: scoped plan / diff, not blind apply
For Terraform or CDK diff in advanced setups, run plan with read-only OIDC into a sandbox account — never apply from the agent PR workflow. Publish the plan artifact for reviewers.
# illustrative — sandbox only
aws sts get-caller-identity
cdk diff "StackName" -c env=sandbox || true
❌ Long-lived AKIA keys in GitHub secrets for agent workflows.
✅ OIDC, sandbox account, deny iam:CreateUser / * on production ARNs via SCP.
Gate 4: CODEOWNERS + required checks for infra
# CODEOWNERS
/infra/ @platform-team
/**/iam*.ts @platform-team
/**/*.tf @platform-team
Require status checks: cheap-gates, iam-lint, cdk-synth. Disable admins bypassing for infra/** if your org allows. Background agent PRs should still wait on platform review — automate the boring catches, not the merge.
Teach the agent the same gates locally
Put non-negotiables in .cursor/rules so background agents do not propose AdministratorAccess in the first place — details in Cursor Rules for AWS CDK. CI remains the backstop when rules are ignored.
<!-- .cursor/rules/aws-iam.mdc excerpt -->
- Never use Action:"*" or Resource:"*" on application roles
- Prefer grant* methods with least privilege; no AdministratorAccess
- No 0.0.0.0/0 on SSH; no inline secrets
- After infra edits, run: pnpm --filter @acme/infra exec cdk synth --strict
For review bots that comment on agent PRs, keep their IAM minimal per AI Code Review Bots. Prod-touching agent tools still need HITL dual control.
Checklist
- [ ] PR CI: affected typecheck + IAM wildcard scan on every agent PR
- [ ]
cdk synth --strict(orterraform validate+ plan) when infra paths change - [ ] CFN/IAM lint with small Sid allowlist; no silent
* - [ ] No production apply credentials on agent workflows; sandbox OIDC only
- [ ] CODEOWNERS for
infra/**and IAM files; required checks enforced - [ ] Cursor rules mirror CI (wildcards, synth command, no fake constructs)
- [ ] Review bot / humans treat agent AWS diffs as hostile until gates pass
- [ ] Alerts when synth/lint start failing on
mainafter rule drift
Related reading
- Cursor Rules for AWS CDK: Stop AI From Inventing IAM Wildcards
- Cursor Rules for TypeScript Monorepos: AI Edits
- AI Code Review Bots: IAM, Secrets, and Least-Privilege Pipelines
- Human-in-the-Loop Gates: Dual Control for Prod-Touching Agent Tools
Last updated on September 11, 2026
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.