11 views
A tenant’s coding-agent role was supposed to be “S3 + Bedrock + one Lambda.” Someone widened it during an incident; the agent later called iam:CreateAccessKey and walked out with long-lived credentials. IAM identity policies alone are not a blast-radius ceiling when humans (or agents) can attach AdministratorAccess. AWS Organizations Service Control Policies (SCPs) are the unfair advantage: OU-level deny guards that apply even when the role is over-permissioned — agent accounts simply cannot call dangerous APIs. Pair with Verified Permissions, IAM condition keys, and Budgets — this post is the account blast-radius ceiling.
⚡ TL;DR: Put each tenant agent runtime in an account (or tightly scoped OU). Attach SCPs that denyiam:*mutations,organizations:LeaveOrganization, broadkms:ScheduleKeyDeletion, and region escapes. SCPs do not grant access — they only clip the max permissions. Related: Verified Permissions Cedar, IAM condition keys, Lambda recursive loop protection.
Why identity policies are not enough
Defense in depth for coding agents:| Layer | Answers | Failure mode if alone |
|---|---|---|
| Cedar / Verified Permissions | May this tool run for this user? | Tool runner still has fat IAM |
| IAM role + condition keys | May this principal call this API on tagged resources? | Human attaches Admin |
| SCP on agent OU | May anyone in this account call this API class? | Nothing — ceiling holds |
| Budgets / AppConfig | Spend + feature kill | Too late for IAM theft |
iam:CreateUser, no role in that account can CreateUser — including AdministratorAccess.
json
// ❌ Relying only on a "tight" agent role — next week someone attaches Admin
{
"Effect": "Allow",
"Action": ["s3:*", "bedrock:*", "iam:*"],
"Resource": "*"
}
OU layout for per-tenant agent accounts
Recommended skeleton:RootSecurity/Shared(no agent workloads)AgentRuntimesOU ← SCP attached hereagent-tenant-acmeagent-tenant-globex
SandboxOU (different SCP, more deny)
organizations:* in identity policies; SCP still denies Leave/Remove.
typescript
// ✅ Conceptual: attach SCP to AgentRuntimes OU (Organizations SDK)
import {
OrganizationsClient,
AttachPolicyCommand,
} from "@aws-sdk/client-organizations";
const org = new OrganizationsClient({});
await org.send(
new AttachPolicyCommand({
PolicyId: "p-agentguard",
TargetId: "ou-xxxx-agentruntimes",
})
);
Deny dangerous actions even if roles are wide
Start with a deny list SCP for agent OUs (full-AWS-access SCP still required elsewhere in the hierarchy so allows can exist).json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyIAMMutationsInAgentAccounts",
"Effect": "Deny",
"Action": [
"iam:CreateUser",
"iam:CreateAccessKey",
"iam:CreateLoginProfile",
"iam:AttachUserPolicy",
"iam:AttachRolePolicy",
"iam:PutRolePolicy",
"iam:UpdateAssumeRolePolicy",
"iam:PassRole"
],
"Resource": "*",
"Condition": {
"ArnNotLike": {
"aws:PrincipalARN": [
"arn:aws:iam::*:role/OrgBreakGlass*",
"arn:aws:iam::*:role/StackSet*"
]
}
}
},
{
"Sid": "DenyLeaveOrg",
"Effect": "Deny",
"Action": [
"organizations:LeaveOrganization"
],
"Resource": "*"
},
{
"Sid": "DenyDangerousAccountMoves",
"Effect": "Deny",
"Action": [
"organizations:DeregisterDelegatedAdministrator",
"organizations:DisableAWSServiceAccess"
],
"Resource": "*"
}
]
}
json
// ✅ Extra: deny regions you do not operate
{
"Sid": "DenyNonHomeRegions",
"Effect": "Deny",
"NotAction": ["sts:*", "cloudformation:*"],
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": ["us-east-1", "eu-west-1"]
}
}
}
python
PassRole deserves special care: agents that can # ❌ SCP that allows iam:* "for convenience" in agent OU — deletes the ceiling
# Never put Allow iam:* on agent runtime OUs via SCP (SCPs rarely Allow anyway;
# the bug is usually forgetting Deny + attaching Admin at identity layer)
iam:PassRole into Lambda/CodeBuild can escalate. Deny PassRole broadly, then allowlist specific roles via condition iam:PassedToService + resource ARNs if you must.
Complement Verified Permissions and IAM condition keys
- Cedar: “user alice may invoke tool
apply_patchon repo X” - IAM conditions: “role may
s3:PutObjectonly ifs3:ExistingObjectTag/tenant=acme” (condition keys post) - SCP: “this account may not call
iam:CreateAccessKeyat all”
typescript
Recursive agent invokes and privilege loops still need Lambda recursive loop protection — SCPs do not stop application-level loops.
// ✅ Runtime still authorizes tools finely
import { VerifiedPermissionsClient, IsAuthorizedCommand } from "@aws-sdk/client-verifiedpermissions";
const vp = new VerifiedPermissionsClient({});
const decision = await vp.send(
new IsAuthorizedCommand({
policyStoreId: process.env.POLICY_STORE!,
principal: { entityType: "User", entityId: userId },
action: { actionType: "Action", actionId: "invokeTool" },
resource: { entityType: "Tool", entityId: "apply_patch" },
})
);
if (decision.decision !== "ALLOW") throw new Error("denied");
// SCP silently ensures even a buggy allow cannot CreateAccessKey
Rollout without locking yourself out
- Create SCP in detachable draft; validate JSON
- Attach to a pilot OU with one non-prod agent account
- Run agent integration suite: Bedrock, S3, Lambda invoke, CodeBuild start
- Watch CloudTrail for implicit denies (
implicitDeny/Org SCP) - Expand OU; keep break-glass roles exempt via
ArnNotLike - Document that management account / stacksets need exclusion paths
bash
Pair spend ceilings with Budgets + Cost Anomaly — SCPs stop IAM escapes; Budgets stop token/GPU escapes.
# ✅ Test effective permissions from agent role
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::TENANT:role/AgentToolRole \
--action-names iam:CreateAccessKey bedrock:InvokeModel s3:PutObject
# Expect CreateAccessKey denied by SCP even if identity allows
What SCPs do not replace
SCPs will not:- Stop an agent from deleting objects in buckets it is allowed to access
- Validate tool arguments (paths, SQL, shell) — that is application + Cedar
- Cap Bedrock token spend — use Budgets / Cost Anomaly and AppConfig
- Fix confused-deputy across accounts without resource policies
bash
# ✅ Continuous control: EventBridge / Config rule on IAM API calls from agent accounts
# Any iam:CreateAccessKey from AgentRuntimes OU → PagerDuty (should be zero)
json
Document the break-glass path offline. If your only recovery role is also denied by SCP without an exemption, you will learn about SCP semantics at the worst possible time. Test restore of a mis-attached SCP quarterly.
{
"Sid": "DenyS3PublicACL",
"Effect": "Deny",
"Action": ["s3:PutObjectAcl", "s3:PutBucketAcl"],
"Resource": "*",
"Condition": {
"StringEquals": {
"s3:x-amz-acl": ["public-read", "public-read-write"]
}
}
}
Checklist
- [ ] Tenant agent workloads live under a dedicated OU with deny-list SCPs
- [ ] Deny IAM mutations, LeaveOrganization, and dangerous org APIs
- [ ] Exempt only break-glass / StackSet roles via tight ARN conditions
- [ ] Keep Cedar + IAM condition keys for fine-grained tool/resource auth
- [ ] Pilot on one account; simulate CreateAccessKey before broad attach
- [ ] Monitor CloudTrail for SCP denies; alert on any IAM API attempts from agent roles
Watch: 100 Days of AI on YouTube
Short videos from CheatCoders, one AI engineering topic per day.
Most viewed
- Python Decorators Explained: From Simple Wrappers to Production Patterns
- Claude Code vs Cursor vs GitHub Copilot: Choose by Workflow, and Set Up One Repo That Works With All Three
- AI Agent Frameworks in 2025: LangGraph vs CrewAI vs AutoGen vs Raw API
- Python String Methods: Every str Method With Real Production Examples
- REST API Design Best Practices: The Patterns That Make APIs a Joy to Use
Newly added
- n8n vs AI Agent: I Gave Both the Same Job (Local, Ollama)
- Local AI Video Generator: Build Your Own App (Free, Python, No GPU)
- AI Wrote Raw Binary Instead of Code. Here’s What Happened (WebAssembly)
- I Built a Browser That Only Runs Encrypted Binary (Ep 1)
- RAG From Scratch: Chunking, Embeddings, Reranking and Evaluation (Real Numbers)
Deep-dive PDF
Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.
Already subscribed? or open the subscribe page.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.