<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shieldly</title>
    <description>The latest articles on DEV Community by Shieldly (@shieldlyio).</description>
    <link>https://dev.to/shieldlyio</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4002311%2F46dd97e3-625c-4be8-8ec0-bf8dcd4c90d4.png</url>
      <title>DEV Community: Shieldly</title>
      <link>https://dev.to/shieldlyio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shieldlyio"/>
    <language>en</language>
    <item>
      <title>4 IAM Misconfiguration Patterns That Keep Showing Up in Real AWS Accounts</title>
      <dc:creator>Shieldly</dc:creator>
      <pubDate>Wed, 15 Jul 2026 13:21:13 +0000</pubDate>
      <link>https://dev.to/shieldlyio/4-iam-misconfiguration-patterns-that-keep-showing-up-in-real-aws-accounts-f9o</link>
      <guid>https://dev.to/shieldlyio/4-iam-misconfiguration-patterns-that-keep-showing-up-in-real-aws-accounts-f9o</guid>
      <description>&lt;p&gt;I spend a lot of time looking at IAM policies. Real ones — from production accounts, staging environments, CI/CD pipelines. After a while, the same mistakes keep appearing. Not because teams are careless. Because IAM's policy language is expressive enough that you can write something that looks reasonable and still be wrong.&lt;/p&gt;

&lt;p&gt;Here are four patterns I keep finding, why they're dangerous, and how to catch them.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. &lt;code&gt;Action: "*"&lt;/code&gt; with &lt;code&gt;Resource: "*"&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is the obvious one, but it hides in less obvious places. It's not always &lt;code&gt;AdministratorAccess&lt;/code&gt;. Sometimes it's a Lambda execution role that started as a placeholder during development. Six months later it's still there, running in production, with full access to every service.&lt;/p&gt;

&lt;p&gt;What to check: Does this role actually need to touch DynamoDB? SQS? KMS? If the Lambda only writes to CloudWatch Logs and reads from one S3 bucket, scope it to those two services.&lt;/p&gt;

&lt;p&gt;The fix isn't &lt;code&gt;Action: "s3:*"&lt;/code&gt; — that still gives the role permission to delete every bucket in the account. Narrow it to the specific actions the function calls: &lt;code&gt;s3:GetObject&lt;/code&gt;, &lt;code&gt;s3:PutObject&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. &lt;code&gt;iam:PassRole&lt;/code&gt; without conditions
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;PassRole&lt;/code&gt; is the permission that says "this principal can pass a role to an AWS service." It's what lets a Lambda function assume its execution role, or an EC2 instance launch with an instance profile.&lt;/p&gt;

&lt;p&gt;Without conditions, &lt;code&gt;PassRole&lt;/code&gt; with a wildcard resource means the principal can pass &lt;em&gt;any&lt;/em&gt; role in the account. A developer with &lt;code&gt;PassRole&lt;/code&gt; on &lt;code&gt;*&lt;/code&gt; can launch an EC2 instance with the account's admin role attached.&lt;/p&gt;

&lt;p&gt;What to check: Every &lt;code&gt;PassRole&lt;/code&gt; statement should have a &lt;code&gt;Resource&lt;/code&gt; field that lists specific role ARNs, and ideally a &lt;code&gt;Condition&lt;/code&gt; with &lt;code&gt;iam:PassedToService&lt;/code&gt; scoping which services can receive the role.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. &lt;code&gt;s3:GetObject&lt;/code&gt; on &lt;code&gt;*&lt;/code&gt; in a role's policy
&lt;/h3&gt;

&lt;p&gt;A role that can read any S3 object in any bucket. Including the bucket where your CloudTrail logs land. Including buckets owned by other teams that might contain customer data.&lt;/p&gt;

&lt;p&gt;This often happens because someone created a role to read from one specific bucket, typed &lt;code&gt;"Resource": "*"&lt;/code&gt; to get it working, and never came back to tighten it.&lt;/p&gt;

&lt;p&gt;What to check: Every &lt;code&gt;s3:GetObject&lt;/code&gt; statement should list specific bucket ARNs. If the role needs access to multiple buckets, list them explicitly. The extra thirty seconds of typing saves you from a blast radius problem later.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Missing condition keys on broad permissions
&lt;/h3&gt;

&lt;p&gt;A role with &lt;code&gt;ec2:TerminateInstances&lt;/code&gt; on &lt;code&gt;*&lt;/code&gt; is a role that can terminate every EC2 instance in the account. Add &lt;code&gt;"Condition": {"StringEquals": {"aws:ResourceTag/Environment": "staging"}}&lt;/code&gt; and suddenly that same permission only applies to instances tagged &lt;code&gt;Environment: staging&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Condition keys like &lt;code&gt;aws:ResourceTag&lt;/code&gt;, &lt;code&gt;aws:PrincipalTag&lt;/code&gt;, and &lt;code&gt;aws:SourceIp&lt;/code&gt; are how you write policies that are broad where they need to be and tight everywhere else. But most policies I see don't use them at all.&lt;/p&gt;

&lt;p&gt;What to check: For any &lt;code&gt;Action&lt;/code&gt; that includes &lt;code&gt;Delete*&lt;/code&gt; or &lt;code&gt;Terminate*&lt;/code&gt;, look at whether a resource tag condition would reduce the blast radius. ABAC (attribute-based access control) isn't just a buzzword — it's how teams with 200+ roles keep permissions manageable.&lt;/p&gt;




&lt;h3&gt;
  
  
  How to catch these systematically
&lt;/h3&gt;

&lt;p&gt;Manual review works for one role. It doesn't scale to fifty. And it definitely doesn't work when policies change across deploys and nobody reviews the diff.&lt;/p&gt;

&lt;p&gt;The Shieldly free demo at &lt;a href="https://shieldly.io/app/iam" rel="noopener noreferrer"&gt;shieldly.io/app/iam&lt;/a&gt; runs AI-Powered analysis on any IAM policy and flags wildcard actions, missing conditions, overly broad resources, and &lt;code&gt;PassRole&lt;/code&gt; risks — no signup needed. Paste a policy and get findings with remediation steps.&lt;/p&gt;

&lt;p&gt;For teams: the CLI (&lt;code&gt;@shieldly/cli&lt;/code&gt;) and GitHub Action slot into CI/CD. The CDK Construct (&lt;code&gt;@shieldly/cdk-guard&lt;/code&gt;) catches issues before deployment. And the VS Code extension flags them while you're writing the policy.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I built Shieldly because I spent too many Friday afternoons staring at JSON policy documents wondering which wildcard was the problem. If these patterns look familiar, give the demo a try.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>iam</category>
      <category>devops</category>
    </item>
    <item>
      <title>The Hidden Cost of Manual IAM Review</title>
      <dc:creator>Shieldly</dc:creator>
      <pubDate>Wed, 15 Jul 2026 00:10:34 +0000</pubDate>
      <link>https://dev.to/shieldlyio/the-hidden-cost-of-manual-iam-review-2f33</link>
      <guid>https://dev.to/shieldlyio/the-hidden-cost-of-manual-iam-review-2f33</guid>
      <description>&lt;h2&gt;
  
  
  The Hidden Cost of Manual IAM Review
&lt;/h2&gt;

&lt;p&gt;Most teams don't track how long they spend reviewing IAM policies. When I started measuring it on my own team, the numbers were worse than I expected.&lt;/p&gt;

&lt;p&gt;A thorough manual review of one IAM policy takes 10 to 15 minutes. Not a quick scan. A real review: read every statement, trace every cross-account trust, verify every condition key, check for privilege escalation paths, confirm the resource ARNs match what you think they should.&lt;/p&gt;

&lt;p&gt;At 4 engineers touching IAM once a week, that's 4 hours a month. 48 hours a year of senior engineers reading JSON documents.&lt;/p&gt;

&lt;p&gt;And that's the optimistic case. Add a security incident. Add an audit. Add the emergency Friday-afternoon policy change that needs review before deploy. The real number is higher.&lt;/p&gt;

&lt;h3&gt;
  
  
  What manual review misses
&lt;/h3&gt;

&lt;p&gt;The problem isn't just the time. It's that humans are bad at repetitive structured-data review, especially under time pressure.&lt;/p&gt;

&lt;p&gt;Here are the things I've seen slip through manual IAM reviews on production systems:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;iam:PassRole&lt;/code&gt; with no condition.&lt;/strong&gt; This is the big one. PassRole lets a principal pass a role to a service â€” and if there's no &lt;code&gt;iam:PassedToService&lt;/code&gt; condition, that role can be passed to any service that accepts roles. Including services the attacker controls. The reviewer saw the action, mentally categorized it as "role stuff," and moved on. It was statement 47 of 52 â€” the reviewer had already been reading policies for 40 minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wildcard resource with sensitive actions.&lt;/strong&gt; &lt;code&gt;s3:*&lt;/code&gt; on &lt;code&gt;Resource: "*"&lt;/code&gt; is obvious. &lt;code&gt;s3:GetObject&lt;/code&gt; on &lt;code&gt;"arn:aws:s3:::*-backup/*"&lt;/code&gt; with a wildcard in the bucket name â€” that's subtle. The reviewer reads it as "restricted to backup buckets" and moves on. But the wildcard means any bucket ending in &lt;code&gt;-backup&lt;/code&gt;, including ones in other accounts if cross-account access is configured.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Missing &lt;code&gt;aws:SourceArn&lt;/code&gt; on Lambda invocation permissions.&lt;/strong&gt; When you grant another service permission to invoke your Lambda function, you need &lt;code&gt;aws:SourceArn&lt;/code&gt; to prevent the confused deputy problem. Without it, any principal in the trusted service's account can invoke your function through that service. This one gets missed because the reviewer focuses on the principal, not the condition block.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CloudFormation-generated role names that don't match the policy's intent.&lt;/strong&gt; A policy grants &lt;code&gt;dynamodb:PutItem&lt;/code&gt; on a table called &lt;code&gt;prod-users&lt;/code&gt;. But the resource ARN references a table with a CloudFormation-generated suffix like &lt;code&gt;prod-users-AB12CD34EF&lt;/code&gt;. The reviewer sees "prod-users" in the logical name and confirms. The physical resource is a different table entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  The pattern: fatigue degrades quality
&lt;/h3&gt;

&lt;p&gt;Everyone starts a review session sharp. The first policy gets 100% attention. By the third policy, you're skimming condition blocks. By the fifth, you're checking actions and resources and hoping nothing weird is in the conditions. This isn't incompetence â€” it's how human attention works on repetitive structured-data tasks.&lt;/p&gt;

&lt;p&gt;The worst-case scenario: the reviewer is 3 tickets behind on a Friday at 4pm. They have a deploy waiting on their approval. They scan the policy in 2 minutes, see nothing obviously wrong, and approve. That's the moment &lt;code&gt;iam:PassRole&lt;/code&gt; with no condition gets through.&lt;/p&gt;

&lt;h3&gt;
  
  
  What automation changes
&lt;/h3&gt;

&lt;p&gt;Automated IAM analysis doesn't get tired. It doesn't have a Friday afternoon. It checks every condition on every statement on every review, regardless of how many policies came before it.&lt;/p&gt;

&lt;p&gt;For our team, switching from manual-only review to automated-first changed two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Review time dropped from 10-15 minutes to 30 seconds per policy.&lt;/strong&gt; The tool flags issues; the human verifies the flags. The human spends time on judgment (is this intentional? does the context justify this wildcard?) instead of discovery (does this policy have a wildcard somewhere?).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Catch rate improved.&lt;/strong&gt; Not because the tool is smarter than a human â€” it's not. Because it doesn't skip statement 47.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Try it yourself
&lt;/h3&gt;

&lt;p&gt;You can test this on your own policies. Paste any IAM policy into Shieldly's free analyzer at shieldly.io/app/iam â€” no signup, no credit card. It'll show you every wildcard, every missing condition, every PassRole without a service restriction, every resource mismatch. Same engine that runs on the paid plans.&lt;/p&gt;

&lt;p&gt;If you want to automate it: Builder ($19/mo) adds API access and the Cost Advisor. Pro ($49/mo) adds the Compliance Panel for audit readiness. Team ($99/mo) adds automated scanning, Slack alerts, and supports up to 10 engineers.&lt;/p&gt;

&lt;p&gt;The math on manual IAM review isn't complicated. 48 hours of senior engineer time per year, plus whatever gets through on Friday afternoons. It's just uncomfortable to calculate.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I built &lt;a href="https://www.shieldly.io" rel="noopener noreferrer"&gt;Shieldly&lt;/a&gt; â€” AI-Powered Security Analysis for AWS. This is real math from measuring my own team's review time, not a benchmark.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>iam</category>
      <category>security</category>
      <category>devops</category>
    </item>
    <item>
      <title>Catch Risky IAM in Your CDK App Before cdk deploy</title>
      <dc:creator>Shieldly</dc:creator>
      <pubDate>Mon, 13 Jul 2026 13:19:00 +0000</pubDate>
      <link>https://dev.to/shieldlyio/catch-risky-iam-in-your-cdk-app-before-cdk-deploy-25gm</link>
      <guid>https://dev.to/shieldlyio/catch-risky-iam-in-your-cdk-app-before-cdk-deploy-25gm</guid>
      <description>&lt;p&gt;You ship a CDK stack. One IAM role has a wildcard because you copy-pasted a gist eight months ago. That role is now your blast radius.&lt;/p&gt;

&lt;p&gt;Most CDK pipelines have linting. They have type checking. They have &lt;code&gt;cdk diff&lt;/code&gt; to preview changes. But nobody's checking IAM policies for overpermissions at synth time.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@shieldly/cdk-guard&lt;/code&gt; fills that gap. It runs AI-Powered security analysis on every synthesized stack: risky IAM policies and CloudFormation misconfigurations get flagged before anything hits CloudFormation.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I work on Shieldly.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it catches
&lt;/h2&gt;

&lt;p&gt;The patterns that manual review misses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Action: "*"&lt;/code&gt; or &lt;code&gt;"*:*"&lt;/code&gt;&lt;/strong&gt;: the obvious one, but easy to miss when buried in a 200-line stack&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;iam:PassRole&lt;/code&gt; with a wildcard resource and no conditions&lt;/strong&gt;: lets a compromised principal hand privileged roles to services it controls&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;s3:*&lt;/code&gt; on a role that only needs &lt;code&gt;GetObject&lt;/code&gt;&lt;/strong&gt;: permission inflation that grows over time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing condition keys&lt;/strong&gt;: no &lt;code&gt;aws:SourceArn&lt;/code&gt;, no &lt;code&gt;aws:PrincipalOrgID&lt;/code&gt;, no constraints at all&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are obscure. They're the patterns that show up in every post-incident review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; @shieldly/cdk-guard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simplest path is the CLI wrapper: it runs &lt;code&gt;cdk synth&lt;/code&gt;, then analyzes all synthesized stacks. Works with any CDK language:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @shieldly/cdk-guard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or add the construct-style guard in a JavaScript/TypeScript CDK app. It analyzes &lt;code&gt;cdk.out/&lt;/code&gt; automatically when the process exits, no explicit call needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ShieldlyGuard&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@shieldly/cdk-guard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ShieldlyGuard&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;failOn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;High&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Critical | High | Medium | Low | none&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MyStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyStack&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If findings at or above &lt;code&gt;failOn&lt;/code&gt; severity come back, the process exits non-zero and your deploy stops.&lt;/p&gt;

&lt;p&gt;You'll need an API key (&lt;code&gt;SHIELDLY_API_KEY&lt;/code&gt; env var, keys are on the Builder plan and above); without a key it runs against the free demo endpoint with a scan limit. Privacy note: your CDK templates are never logged, cache keys are one-way SHA-256 hashes.&lt;/p&gt;

&lt;h2&gt;
  
  
  CI/CD integration
&lt;/h2&gt;

&lt;p&gt;Block bad policies before they reach &lt;code&gt;main&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# GitHub Actions example&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CDK security check&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx @shieldly/cdk-guard&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;SHIELDLY_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SHIELDLY_API_KEY }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's also a &lt;code&gt;cdk.json&lt;/code&gt; hook variant (&lt;code&gt;"afterSynth": ["npx", "@shieldly/cdk-guard", "--no-synth"]&lt;/code&gt;) if you want it to run on every synth regardless of who invokes it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;API keys start on the Builder plan ($19/mo, 150 analysis units/day). There's also a free tier (20 analysis units/day) and a keyless demo mode with a scan limit, so you can try it before paying anything.&lt;/p&gt;

&lt;p&gt;The package is on &lt;a href="https://npmjs.com/package/@shieldly/cdk-guard" rel="noopener noreferrer"&gt;npm&lt;/a&gt;. Source is on &lt;a href="https://github.com/shieldly-io/shieldly" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cdk</category>
      <category>security</category>
      <category>devops</category>
    </item>
    <item>
      <title>Why "Request a Demo" Is the Wrong First Step for IAM Security Tools</title>
      <dc:creator>Shieldly</dc:creator>
      <pubDate>Fri, 10 Jul 2026 13:18:29 +0000</pubDate>
      <link>https://dev.to/shieldlyio/why-request-a-demo-is-the-wrong-first-step-for-iam-security-tools-49fg</link>
      <guid>https://dev.to/shieldlyio/why-request-a-demo-is-the-wrong-first-step-for-iam-security-tools-49fg</guid>
      <description>&lt;p&gt;I built an IAM policy analyzer. The most common first question wasn't about features. It was "what happens to my policies after I paste them?"&lt;/p&gt;

&lt;p&gt;Fair question. You're handing over your infrastructure's permission model to a text box. If the vendor can't answer that question clearly, you shouldn't trust them with anything else.&lt;/p&gt;

&lt;p&gt;But there's a second problem most security tools create: they gate the answer behind a demo call.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Demo Call Problem
&lt;/h3&gt;

&lt;p&gt;"Request a demo" means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You fill out a form&lt;/li&gt;
&lt;li&gt;Someone emails you a Calendly link&lt;/li&gt;
&lt;li&gt;You spend 30 minutes on a screen share with a sales engineer&lt;/li&gt;
&lt;li&gt;They show you a sandboxed environment with synthetic data&lt;/li&gt;
&lt;li&gt;Your actual IAM policy never touches the product&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You walk away knowing how the tool &lt;em&gt;looks&lt;/em&gt;, not how it &lt;em&gt;works&lt;/em&gt; on your infrastructure.&lt;/p&gt;

&lt;p&gt;Real demo mode â€” the kind where you paste an actual policy and get real findings â€” answers three questions immediately:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Does this tool find issues I actually have?&lt;/strong&gt; Not curated examples. Your policies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What does the output look like?&lt;/strong&gt; Is it actionable or a wall of text?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is the analysis fast enough to fit into my workflow?&lt;/strong&gt; Or do I need to context-switch and come back?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A sales demo answers none of these.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Real Demo Mode Requires
&lt;/h3&gt;

&lt;p&gt;Building a tool anyone can use without signing up means solving a few problems:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Input privacy.&lt;/strong&gt; Your policies can't be logged. Shieldly uses a one-way SHA-256 hash of your input as a cache key. The plaintext never hits a log, never lands in a training set, never sits in an analytics database. The hash lets us return cached results for identical policies without storing the policies themselves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rate limiting that doesn't break the experience.&lt;/strong&gt; You need enough free analyses to evaluate the tool (20 analysis units per day on the free tier) without opening a vector for abuse. Server-side caps, not localStorage counters that anyone can clear.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real analysis, not a reduced model.&lt;/strong&gt; Some tools run a lighter model for demo traffic. Shieldly runs the same analysis pipeline whether you're signed in or not â€” Standard AI for free users, Advanced and Enterprise for paid tiers. The demo isn't a downgraded experience. It's the real thing, just gated by a daily cap.&lt;/p&gt;

&lt;h3&gt;
  
  
  Evaluating a Security Tool Without a Demo Call
&lt;/h3&gt;

&lt;p&gt;If a tool has real demo mode, you can test it in 5 minutes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Paste a policy you already reviewed manually.&lt;/strong&gt; You know what the issues are. Does the tool catch them? Does it find things you missed?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paste a policy you know is clean.&lt;/strong&gt; Does it produce false positives? A tool that flags &lt;code&gt;s3:ListBucket&lt;/code&gt; as a finding on a read-only reporting role isn't useful.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the findings format.&lt;/strong&gt; Are findings grouped by severity? Does each one explain &lt;em&gt;why&lt;/em&gt; it's a problem and &lt;em&gt;how&lt;/em&gt; to fix it? If the output is just "this policy has 7 issues" with no detail, you'll need a second tool to actually remediate.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Try It With Your Own Policy
&lt;/h3&gt;

&lt;p&gt;Shieldly's IAM analyzer is free at shieldly.io/app/iam. No signup, no credit card. Paste any IAM policy â€” resource policy, identity policy, SCP â€” and get real findings in seconds.&lt;/p&gt;

&lt;p&gt;The free tier gives you 20 analysis units per day. Builder ($19/mo) adds API access and Cost Advisor. Pro ($49/mo) adds a compliance panel. Team ($99/mo) adds auto-scanning and Slack alerts for up to 10 engineers.&lt;/p&gt;

&lt;p&gt;If you paste a policy and the findings aren't useful, that's honest feedback I want to hear. If they are useful, you just evaluated a security tool in less time than a demo call would have taken.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>iam</category>
      <category>security</category>
      <category>devops</category>
    </item>
    <item>
      <title>AWS IAM Privilege Escalation: Real Attack Paths DevOps Engineers Need to Know</title>
      <dc:creator>Shieldly</dc:creator>
      <pubDate>Tue, 07 Jul 2026 13:16:20 +0000</pubDate>
      <link>https://dev.to/shieldlyio/aws-iam-privilege-escalation-real-attack-paths-devops-engineers-need-to-know-1mo6</link>
      <guid>https://dev.to/shieldlyio/aws-iam-privilege-escalation-real-attack-paths-devops-engineers-need-to-know-1mo6</guid>
      <description>&lt;p&gt;You've locked down your AWS accounts. MFA is enforced, no wildcard IAM policies in production, and you've got a service control policy (SCP) boundary that's tighter than a production change freeze. Feels safe, right?&lt;/p&gt;

&lt;p&gt;Then someone on your team grants &lt;code&gt;iam:PassRole&lt;/code&gt; to a Lambda function, and a junior engineer accidentally leaves &lt;code&gt;iam:CreatePolicyVersion&lt;/code&gt; on a developer role. Two harmless-looking permissions. One breach.&lt;/p&gt;

&lt;p&gt;IAM privilege escalation in AWS rarely comes from obvious admin access. It comes from chains — individually reasonable permissions that, when combined, let an attacker with a foothold pivot to full account compromise. Here are the escalation paths I've seen pop up in real AWS environments, what they look like in practice, and how to shut them down without breaking your team's workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Path 1: &lt;code&gt;iam:PassRole&lt;/code&gt; → Lambda → &lt;code&gt;sts:AssumeRole&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This is the most common escalation I encounter during reviews. It's deceptively simple.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The dangerous permission set:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"iam:PassRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lambda:CreateFunction"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lambda:InvokeFunction"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On its own, &lt;code&gt;iam:PassRole&lt;/code&gt; lets an IAM principal attach an existing IAM role to an AWS service. It doesn't grant any permissions on that role's policies — just the ability to &lt;em&gt;attach&lt;/em&gt; it. Engineers add this to Lambda execution roles all the time so CI/CD pipelines can deploy functions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The escalation:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An attacker who compromises a principal with those three permissions can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find a high-privilege IAM role in the account (e.g., &lt;code&gt;AdminRole&lt;/code&gt;, &lt;code&gt;DeploymentRole&lt;/code&gt;, &lt;code&gt;BreakGlassRole&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Create a Lambda function with that role attached via &lt;code&gt;iam:PassRole&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Write a one-liner in the function that calls &lt;code&gt;sts:AssumeRole&lt;/code&gt; on itself — or, more destructively, calls &lt;code&gt;iam:CreateAccessKey&lt;/code&gt; on an admin user.&lt;/li&gt;
&lt;li&gt;Invoke the function, extract the credentials, and own the role.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# payload.py — deployed as Lambda, attached to the victim role
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;sts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sts&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Just long enough to exfil
&lt;/span&gt;    &lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_session_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DurationSeconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;done&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;access&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Credentials&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AccessKeyId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mitigation isn't "never use &lt;code&gt;iam:PassRole&lt;/code&gt;" — that's impractical. The fix is &lt;strong&gt;resource constraints&lt;/strong&gt; on &lt;code&gt;iam:PassRole&lt;/code&gt; to limit what roles can be passed, and &lt;code&gt;lambda:CreateFunction&lt;/code&gt; to restrict what functions can be created. Pair this with a permissions boundary on Lambda execution roles, and the path collapses.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"iam:PassRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::*:role/ci-cd-*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"StringEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"iam:PassedToService"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lambda.amazonaws.com"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Path 2: EC2 Instance Profile Chaining
&lt;/h2&gt;

&lt;p&gt;EC2 instance profiles are the original IAM escalation vector, and they're still widespread. The mechanics are different from the Lambda path but the endgame is the same: hijacking an attached role's identity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The setup:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your CI/CD runners live on EC2. Each runner gets an instance profile role — say &lt;code&gt;EC2-CI-Runner&lt;/code&gt; — with permissions to pull from ECR, write to CloudWatch, and access an S3 bucket for build artifacts. Standard stuff.&lt;/p&gt;

&lt;p&gt;Someone adds &lt;code&gt;ec2:AssociateIamInstanceProfile&lt;/code&gt; to a developer role so the team can attach profiles to test instances during development. That's the crack in the door.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The escalation:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An attacker with &lt;code&gt;ec2:AssociateIamInstanceProfile&lt;/code&gt; (and &lt;code&gt;ec2:RunInstances&lt;/code&gt; or access to an existing instance) can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Launch a new EC2 instance (or hijack an existing stopped one they can &lt;code&gt;ec2:StartInstances&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Detach the low-privilege instance profile from the target instance.&lt;/li&gt;
&lt;li&gt;Associate a high-privilege instance profile — one attached to a production service or a data pipeline — onto their instance.&lt;/li&gt;
&lt;li&gt;SSH in (via SSM or a key they control) and retrieve the instance metadata credentials at &lt;code&gt;http://169.254.169.254/latest/meta-data/iam/security-credentials/&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One &lt;code&gt;curl&lt;/code&gt; command later, they have &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt;, &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;, and &lt;code&gt;AWS_SESSION_TOKEN&lt;/code&gt; for the production role.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# On the attacker-controlled EC2 instance&lt;/span&gt;
&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; PUT &lt;span class="s2"&gt;"http://169.254.169.254/latest/api/token"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-aws-ec2-metadata-token-ttl-seconds: 21600"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-aws-ec2-metadata-token: &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://169.254.169.254/latest/meta-data/iam/security-credentials/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Never grant &lt;code&gt;ec2:AssociateIamInstanceProfile&lt;/code&gt; without a resource constraint on the &lt;strong&gt;instance profile ARN&lt;/strong&gt;, not just the instance. Lock it down to specific profiles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ec2:AssociateIamInstanceProfile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:ec2:*:*:instance/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::*:instance-profile/development-*"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better yet, add a &lt;code&gt;Condition&lt;/code&gt; key checking that the profile isn't tagged as &lt;code&gt;production&lt;/code&gt;, or apply a service control policy that blocks instance profile association for non-admin roles entirely.&lt;/p&gt;




&lt;h2&gt;
  
  
  Path 3: &lt;code&gt;iam:CreatePolicyVersion&lt;/code&gt; — The Nearly Silent Takeover
&lt;/h2&gt;

&lt;p&gt;This one flies under the radar because it's &lt;em&gt;one permission&lt;/em&gt; — no chaining required.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;iam:CreatePolicyVersion&lt;/code&gt; lets you create a new version of an existing customer-managed policy. If the policy is attached to a user, group, or role, the new version can be set as default, instantly granting new permissions to every principal under that policy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The scenario:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A security-conscious team grants &lt;code&gt;iam:CreatePolicyVersion&lt;/code&gt; to a handful of trusted engineers so they can iterate on policy changes without going through a full deployment pipeline. The policy in question, &lt;code&gt;SupportEngineerPolicy&lt;/code&gt;, is attached to 40 support engineers who need read-only access to CloudWatch and limited EC2 describe permissions.&lt;/p&gt;

&lt;p&gt;An attacker compromises one support engineer's credentials — phishing, leaked token, compromised workstation — and discovers they can create policy versions. They craft a new version of &lt;code&gt;SupportEngineerPolicy&lt;/code&gt; that adds &lt;code&gt;"Effect": "Allow", "Action": "*", "Resource": "*"&lt;/code&gt; and set it as default.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# One API call to own the environment&lt;/span&gt;
aws iam create-policy-version &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--policy-arn&lt;/span&gt; arn:aws:iam::123456789012:policy/SupportEngineerPolicy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--policy-document&lt;/span&gt; &lt;span class="s1"&gt;'{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"*","Resource":"*"}]}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set-as-default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every principal attached to that policy — 40 engineers — now has full administrative access. The attacker exfiltrates data, creates backdoor users, and the blast radius is enormous. The worst part? No new IAM users or roles were created. No obvious anomalies in CloudTrail if you're not watching &lt;code&gt;CreatePolicyVersion&lt;/code&gt; events.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The mitigation:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Block &lt;code&gt;iam:CreatePolicyVersion&lt;/code&gt; with an SCP for all non-admin roles. Use &lt;code&gt;iam:SetDefaultPolicyVersion&lt;/code&gt; as a sensitive action that triggers an alert in your SIEM. Treat policy version management the same way you treat IAM user creation — it's a privileged operation, not a convenience feature.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Deny"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"iam:CreatePolicyVersion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"iam:SetDefaultPolicyVersion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"iam:DeletePolicyVersion"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ArnNotLike"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"aws:PrincipalARN"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::*:role/admin-*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Putting It Together: Defense in Depth for IAM
&lt;/h2&gt;

&lt;p&gt;If there's one lesson from years of AWS IR, it's this: &lt;strong&gt;IAM permissions are directional.&lt;/strong&gt; You can't audit them in isolation. A permission that's harmless in role A becomes critical when paired with role B's pass-role target or role C's policy attachment.&lt;/p&gt;

&lt;p&gt;What works in practice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SCP boundaries first.&lt;/strong&gt; Deny &lt;code&gt;iam:PassRole&lt;/code&gt; for cross-account roles, deny &lt;code&gt;iam:CreatePolicyVersion&lt;/code&gt; broadly, and restrict instance profile associations at the organization level. You can't accidentally grant what SCPs already block.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Resource-level constraints on everything.&lt;/strong&gt; &lt;code&gt;"Resource": "*"&lt;/code&gt; on pass-role or create-function is a risk acceptance, not a policy. Scope every actionable IAM permission to specific ARN patterns.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Alert on the escalation primitives.&lt;/strong&gt; CloudWatch Events on &lt;code&gt;CreatePolicyVersion&lt;/code&gt;, &lt;code&gt;PassRole&lt;/code&gt; to non-standard services, and &lt;code&gt;AssociateIamInstanceProfile&lt;/code&gt; on production-tagged instances. Don't wait for a breach to find out your monitoring had a gap.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Audit with an adversarial mindset.&lt;/strong&gt; Don't ask "does this role have too many permissions?" Ask "if this role is compromised, what can the attacker reach from here?" Map the chains, not just the leaf permissions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use permissions boundaries.&lt;/strong&gt; Every role your team creates should have a permissions boundary that caps its effective permissions regardless of what policies get attached later. It's the IAM equivalent of a circuit breaker.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;IAM privilege escalation isn't a vulnerability in AWS — it's a vulnerability in &lt;em&gt;how we compose&lt;/em&gt; IAM permissions day to day. The tools themselves are fine. The chains we build with them are the problem.&lt;/p&gt;

&lt;p&gt;Know your chains. Test them before an attacker does.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>iam</category>
      <category>devops</category>
    </item>
    <item>
      <title>9 AWS IAM Privilege Escalation Methods You Can Check in 5 Minutes</title>
      <dc:creator>Shieldly</dc:creator>
      <pubDate>Mon, 06 Jul 2026 10:19:37 +0000</pubDate>
      <link>https://dev.to/shieldlyio/9-aws-iam-privilege-escalation-methods-you-can-check-in-5-minutes-4n0p</link>
      <guid>https://dev.to/shieldlyio/9-aws-iam-privilege-escalation-methods-you-can-check-in-5-minutes-4n0p</guid>
      <description>&lt;p&gt;Most AWS IAM breaches do not start with a zero-day. They start with a policy that&lt;br&gt;
granted one permission too many — and that single permission turned out to be a path to&lt;br&gt;
administrator. Here are nine real privilege-escalation methods, what each one needs, and&lt;br&gt;
how to close it. Each links to a deeper write-up with an example vulnerable policy and the&lt;br&gt;
exploit command.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-service to admin
&lt;/h2&gt;

&lt;p&gt;These need nothing but a permission on your own identity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;iam:AttachUserPolicy&lt;/code&gt;&lt;/strong&gt; — attach &lt;code&gt;AdministratorAccess&lt;/code&gt; to your own user.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;iam:PutUserPolicy&lt;/code&gt;&lt;/strong&gt; — write an inline &lt;code&gt;"Action": "*"&lt;/code&gt; policy on yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.shieldly.io/iam/iam-updateloginprofile" rel="noopener noreferrer"&gt;&lt;code&gt;iam:UpdateLoginProfile&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; —
reset the console password of a more-privileged user and sign in as them (unless they
have MFA).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.shieldly.io/iam/iam-attachgrouppolicy" rel="noopener noreferrer"&gt;&lt;code&gt;iam:AttachGroupPolicy&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; /
&lt;strong&gt;&lt;a href="https://www.shieldly.io/iam/iam-putgrouppolicy" rel="noopener noreferrer"&gt;&lt;code&gt;iam:PutGroupPolicy&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; — attach or
inline admin onto a group you belong to; every member inherits it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Role-based, if you can assume the role
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.shieldly.io/iam/iam-attachrolepolicy" rel="noopener noreferrer"&gt;&lt;code&gt;iam:AttachRolePolicy&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; /
&lt;strong&gt;&lt;a href="https://www.shieldly.io/iam/iam-putrolepolicy" rel="noopener noreferrer"&gt;&lt;code&gt;iam:PutRolePolicy&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; — grant admin to
a role you can assume.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  PassRole + a compute service
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;iam:PassRole&lt;/code&gt; is the most abused permission on AWS because it is the bridge between&lt;br&gt;
"I can configure a service" and "that service runs as a privileged role":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.shieldly.io/iam/passrole-ecs" rel="noopener noreferrer"&gt;ECS&lt;/a&gt;&lt;/strong&gt; — register a task definition whose
task role is privileged, run it, read the credentials from the task metadata endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.shieldly.io/iam/lambda-eventsourcemapping" rel="noopener noreferrer"&gt;Lambda event source mapping&lt;/a&gt;&lt;/strong&gt;
— create a function with a privileged execution role and trigger it from a stream, no
&lt;code&gt;lambda:InvokeFunction&lt;/code&gt; required.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Already on the box
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.shieldly.io/iam/ssm-sendcommand" rel="noopener noreferrer"&gt;&lt;code&gt;ssm:SendCommand&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; — run commands on an
EC2 instance that already holds a privileged instance-profile role, and use its
credentials off-box. No &lt;code&gt;iam:PassRole&lt;/code&gt; needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The common fix
&lt;/h2&gt;

&lt;p&gt;Across all of these the pattern is the same: scope &lt;code&gt;iam:PassRole&lt;/code&gt; with an&lt;br&gt;
&lt;code&gt;iam:PassedToService&lt;/code&gt; condition and a role allowlist, restrict the policy/role-management&lt;br&gt;
actions to administrators, and apply a &lt;strong&gt;permissions boundary&lt;/strong&gt; so an attached admin policy&lt;br&gt;
can never exceed the boundary.&lt;/p&gt;

&lt;p&gt;You can paste a policy into &lt;a href="https://www.shieldly.io/app/iam" rel="noopener noreferrer"&gt;Shieldly&lt;/a&gt; and get AI-Powered&lt;br&gt;
analysis of exactly which of these paths it opens and the conditioned fix — free, no signup.&lt;br&gt;
The full reference of escalation methods is at&lt;br&gt;
&lt;a href="https://www.shieldly.io/iam" rel="noopener noreferrer"&gt;shieldly.io/iam&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I help build Shieldly.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>devops</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Gate Insecure AWS IAM in Pull Requests with a Free GitHub Action</title>
      <dc:creator>Shieldly</dc:creator>
      <pubDate>Sun, 05 Jul 2026 13:15:02 +0000</pubDate>
      <link>https://dev.to/shieldlyio/gate-insecure-aws-iam-in-pull-requests-with-a-free-github-action-2pln</link>
      <guid>https://dev.to/shieldlyio/gate-insecure-aws-iam-in-pull-requests-with-a-free-github-action-2pln</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.shieldly.io/blog/" rel="noopener noreferrer"&gt;shieldly.io/blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most IAM problems are not caught by a clever attacker. They are caught â€” eventually â€” by an audit, months after a wildcard shipped because a deploy was blocked at 6pm. The fix is to move the check left: review the policy in the pull request, automatically, before it merges.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the PR Is the Right Place
&lt;/h2&gt;

&lt;p&gt;By the time a misconfiguration reaches a quarterly security review, it has been live for a quarter. The PR is the moment the author has full context, the change is small, and tightening a policy costs one comment instead of a migration project.&lt;/p&gt;

&lt;p&gt;A CI gate also takes the awkward human conversation off the table â€” the bot flags it, consistently, every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add the Action in 3 Lines
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/shieldly.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;IAM Security Analysis&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;analyze&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shieldly-io/action@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;fail-on-severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HIGH&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SHIELDLY_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SHIELDLY_API_KEY }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Action scans changed IAM policies, resource-based policies, and CloudFormation templates in the PR, posts findings as a review comment with the why and the suggested fix, and fails the check on HIGH or CRITICAL findings so risky access cannot merge unnoticed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Catches
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Wildcard &lt;code&gt;Action: *&lt;/code&gt; and &lt;code&gt;Resource: *&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Privilege escalation paths (PassRole chains, CreatePolicyVersion, AttachUserPolicy abuse)&lt;/li&gt;
&lt;li&gt;Trust policy misconfigurations (Principal: *, missing ExternalId)&lt;/li&gt;
&lt;li&gt;CloudFormation roles with AdministratorAccess&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;iam:PassRole&lt;/code&gt; on &lt;code&gt;Resource: *&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NotAction&lt;/code&gt; + &lt;code&gt;Effect: Allow&lt;/code&gt; patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configuration Options
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shieldly-io/action@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;**/*.json,**/*.yaml,cdk.out/**/*.template.json"&lt;/span&gt;
    &lt;span class="na"&gt;fail-on-severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HIGH&lt;/span&gt;   &lt;span class="c1"&gt;# CRITICAL, HIGH, MEDIUM, LOW&lt;/span&gt;
    &lt;span class="na"&gt;comment-on-pr&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;      &lt;span class="c1"&gt;# Post findings as PR comment (default: true)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Free Tier
&lt;/h2&gt;

&lt;p&gt;The Action works on the free plan â€” no credit card, no per-seat charge. Each PR scan uses analysis units from your daily quota. The free plan includes 20 analysis units/day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Launch offer:&lt;/strong&gt; code &lt;code&gt;90Off2M&lt;/code&gt; â€” 90% off first 2 months. Builder from $1.90/mo, includes 150 analysis units/day and priority scan queuing.&lt;/p&gt;

&lt;p&gt;Install: &lt;a href="https://github.com/shieldly-io/action" rel="noopener noreferrer"&gt;github.com/shieldly-io/action&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Catch IAM risks automatically â€” paste a policy into &lt;a href="https://www.shieldly.io/app/iam" rel="noopener noreferrer"&gt;Shieldly's free AI-Powered analysis&lt;/a&gt;. No signup, no credit card.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>devops</category>
      <category>ci</category>
    </item>
    <item>
      <title>AWS STS ExternalId and the Confused Deputy Problem: A Practical Guide</title>
      <dc:creator>Shieldly</dc:creator>
      <pubDate>Wed, 01 Jul 2026 13:20:01 +0000</pubDate>
      <link>https://dev.to/shieldlyio/aws-sts-externalid-and-the-confused-deputy-problem-a-practical-guide-2j39</link>
      <guid>https://dev.to/shieldlyio/aws-sts-externalid-and-the-confused-deputy-problem-a-practical-guide-2j39</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.shieldly.io/blog/" rel="noopener noreferrer"&gt;shieldly.io/blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cross-account IAM roles are the standard mechanism for granting third-party services access to your AWS account. The problem is that naming an entire AWS account as the trusted principal is not specific enough. Any identity authenticating from that account can call &lt;code&gt;sts:AssumeRole&lt;/code&gt; on your role. This is the confused deputy problem, and &lt;code&gt;ExternalId&lt;/code&gt; is the condition that closes it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Confused Deputy Problem Is
&lt;/h2&gt;

&lt;p&gt;The confused deputy is a class of vulnerability where a privileged program is tricked by a less-privileged caller into performing actions on the caller's behalf.&lt;/p&gt;

&lt;p&gt;In AWS: a SaaS provider has a trusted AWS account. Customers create cross-account roles naming the SaaS provider's account as the trusted principal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"AWS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::VENDOR-ACCOUNT:root"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sts:AssumeRole"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any identity in the vendor's account can call &lt;code&gt;sts:AssumeRole&lt;/code&gt; on any customer's role — because from AWS's perspective, the call originates from the correct trusted account. Customer A can potentially assume Customer B's role if they know B's role ARN. The SaaS provider is the confused deputy.&lt;/p&gt;

&lt;h2&gt;
  
  
  How ExternalId Fixes It
&lt;/h2&gt;

&lt;p&gt;When a vendor requires &lt;code&gt;ExternalId&lt;/code&gt; as a condition on &lt;code&gt;sts:AssumeRole&lt;/code&gt;, they generate a unique secret value per customer and store it. When their service assumes your role, it passes that value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"AWS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::VENDOR-ACCOUNT:root"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sts:AssumeRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"StringEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sts:ExternalId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a7f3b2c9-unique-per-customer"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A different customer cannot assume your role because they do not know your &lt;code&gt;ExternalId&lt;/code&gt; — even if they use the same vendor account.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing It Correctly
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;ExternalId&lt;/code&gt; must be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unique per customer-vendor relationship&lt;/strong&gt; — not shared across customers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unguessable&lt;/strong&gt; — a UUID or similar high-entropy value, not a predictable string like a customer name or account ID&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generated by the vendor&lt;/strong&gt; — not chosen by the customer (a customer-chosen ID is potentially guessable by other customers of the same vendor)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also verify that you cannot pass an arbitrary &lt;code&gt;ExternalId&lt;/code&gt; to the vendor's service. If the vendor accepts any value you provide, the control is ineffective.&lt;/p&gt;

&lt;h2&gt;
  
  
  Red Flags in Third-Party Integration Docs
&lt;/h2&gt;

&lt;p&gt;Watch for these in vendor setup instructions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No mention of &lt;code&gt;ExternalId&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Instructions to trust &lt;code&gt;arn:aws:iam::VENDOR-ACCOUNT:root&lt;/code&gt; with no condition&lt;/li&gt;
&lt;li&gt;A fixed, static &lt;code&gt;ExternalId&lt;/code&gt; shared across all customers (defeats the purpose)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Catch confused deputy risks automatically — paste a cross-account trust policy into &lt;a href="https://www.shieldly.io/app/iam" rel="noopener noreferrer"&gt;Shieldly's free AI-Powered analysis&lt;/a&gt;. No signup, no credit card.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Launch offer: code &lt;code&gt;90Off2M&lt;/code&gt; — 90% off first 2 months. Builder from $1.90/mo. &lt;a href="https://www.shieldly.io/pricing" rel="noopener noreferrer"&gt;shieldly.io/pricing&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>iam</category>
      <category>cloud</category>
    </item>
    <item>
      <title>The 7 IAM Misconfigurations We See in Almost Every AWS Account</title>
      <dc:creator>Shieldly</dc:creator>
      <pubDate>Wed, 01 Jul 2026 02:33:59 +0000</pubDate>
      <link>https://dev.to/shieldlyio/the-7-iam-misconfigurations-we-see-in-almost-every-aws-account-35fm</link>
      <guid>https://dev.to/shieldlyio/the-7-iam-misconfigurations-we-see-in-almost-every-aws-account-35fm</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.shieldly.io/blog/" rel="noopener noreferrer"&gt;shieldly.io/blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After analyzing a lot of IAM policies, the same seven patterns show up again and again. Here is each one, why it is dangerous, and the fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Action: * on Resource: *
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is administrator access by another name. If you see it outside a dedicated, tightly controlled admin role, treat it as a critical finding. Fix: enumerate the exact actions the workload needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. s3:* on Resource: *
&lt;/h2&gt;

&lt;p&gt;Even scoping to one service is too broad. &lt;code&gt;s3:*&lt;/code&gt; includes &lt;code&gt;s3:DeleteObject&lt;/code&gt;, &lt;code&gt;s3:DeleteBucket&lt;/code&gt;, and &lt;code&gt;s3:PutBucketPolicy&lt;/code&gt;. Fix: list only the specific actions (&lt;code&gt;s3:GetObject&lt;/code&gt;, &lt;code&gt;s3:PutObject&lt;/code&gt;, etc.) on the specific bucket ARN.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. iam:PassRole on Resource: *
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"iam:PassRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;PassRole&lt;/code&gt; is harmless until paired with a service launch. On &lt;code&gt;Resource: *&lt;/code&gt; it lets a principal pass any role in the account to any service — including admin roles. Fix: scope to specific role ARNs and add the &lt;code&gt;iam:PassedToService&lt;/code&gt; condition key.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Trust policy Principal: *
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any AWS identity in the world can attempt to assume the role. Almost always unintentional. Fix: replace with explicit account or role ARNs, and add an &lt;code&gt;ExternalId&lt;/code&gt; condition for cross-account access.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Missing ExternalId on third-party cross-account roles
&lt;/h2&gt;

&lt;p&gt;Without &lt;code&gt;ExternalId&lt;/code&gt;, any customer of the same vendor can assume your role (the confused deputy problem). Fix: require a unique &lt;code&gt;ExternalId&lt;/code&gt; condition on every cross-account trust policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Inline policies on IAM users
&lt;/h2&gt;

&lt;p&gt;Inline policies do not appear in the IAM managed policy list and are easy to miss in audits. They also travel with the user — if the user is deleted and a new one created with the same name, they do not transfer. Fix: move to customer-managed policies attached to roles, not users.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. sts:AssumeRole on Resource: *
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sts:AssumeRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A principal with this permission can assume any role in the account whose trust policy allows it. That includes roles with administrator access. Fix: scope to specific role ARNs.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Catch all seven automatically — paste a policy into &lt;a href="https://www.shieldly.io/app/iam" rel="noopener noreferrer"&gt;Shieldly's free AI-Powered analysis&lt;/a&gt;. No signup, no credit card.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Launch offer: code &lt;code&gt;90Off2M&lt;/code&gt; — 90% off first 2 months. Builder from $1.90/mo. &lt;a href="https://www.shieldly.io/pricing" rel="noopener noreferrer"&gt;shieldly.io/pricing&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>iam</category>
      <category>devops</category>
    </item>
    <item>
      <title>Securing S3 Bucket Policies: Public Access, Conditions, and Common Mistakes</title>
      <dc:creator>Shieldly</dc:creator>
      <pubDate>Thu, 25 Jun 2026 12:24:23 +0000</pubDate>
      <link>https://dev.to/shieldlyio/securing-s3-bucket-policies-public-access-conditions-and-common-mistakes-1kbe</link>
      <guid>https://dev.to/shieldlyio/securing-s3-bucket-policies-public-access-conditions-and-common-mistakes-1kbe</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.shieldly.io/blog/" rel="noopener noreferrer"&gt;shieldly.io/blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;S3 bucket policies are written once and forgotten. They survive team changes, architecture pivots, and migration projects — and they accumulate permissions that nobody intended to leave open. S3 misconfigurations have been behind some of the largest cloud data breaches on record.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why S3 Bucket Policies Matter
&lt;/h2&gt;

&lt;p&gt;Bucket policies are resource-based policies attached to the bucket itself. They can grant access to principals that have no IAM permissions at all — including anonymous callers — if the policy allows it. A single misconfigured statement can override months of careful IAM work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three Most Dangerous Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Principal: * with no condition
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::my-bucket/*"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An unconditioned wildcard principal means any AWS account, any IAM entity, and any unauthenticated caller can invoke the allowed actions. On a public S3 endpoint this is the open internet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Add a condition — &lt;code&gt;aws:PrincipalOrgID&lt;/code&gt;, &lt;code&gt;aws:SourceArn&lt;/code&gt;, or &lt;code&gt;aws:PrincipalArn&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::my-bucket/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"StringEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"aws:PrincipalOrgID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"o-yourorgid123"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Missing s3:SecureTransport condition
&lt;/h3&gt;

&lt;p&gt;Without &lt;code&gt;aws:SecureTransport: true&lt;/code&gt;, the policy permits access over unencrypted HTTP. Credentials and data travel in plaintext. Add this to every Allow statement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Bool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"aws:SecureTransport"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Overly broad Action grants
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:*"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;s3:*&lt;/code&gt; includes &lt;code&gt;s3:DeleteObject&lt;/code&gt;, &lt;code&gt;s3:PutBucketPolicy&lt;/code&gt;, and &lt;code&gt;s3:DeleteBucket&lt;/code&gt;. A policy intended to share read access becomes a full-control grant. Replace with the exact actions needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  S3 Block Public Access Interaction
&lt;/h2&gt;

&lt;p&gt;Block Public Access (BPA) settings at the account and bucket level override bucket policies. But they only block policies that grant access to &lt;code&gt;Principal: *&lt;/code&gt; — a policy granting access to a specific external account bypasses BPA entirely. BPA is not a substitute for correct bucket policy authoring.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Secure Bucket Policy Looks Like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"AWS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::123456789012:role/my-app-role"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::my-bucket/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Bool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"aws:SecureTransport"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Deny"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::my-bucket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::my-bucket/*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Bool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"aws:SecureTransport"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"false"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explicit principal, scoped actions, HTTPS enforced, deny-on-HTTP for belt-and-suspenders.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Analyze S3 bucket policies automatically — paste into &lt;a href="https://www.shieldly.io/app/iam" rel="noopener noreferrer"&gt;Shieldly's free AI-Powered analysis&lt;/a&gt;. No signup, no credit card.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Launch offer: code &lt;code&gt;90Off2M&lt;/code&gt; — 90% off first 2 months. Pro from $1.90/mo. &lt;a href="https://www.shieldly.io/pricing" rel="noopener noreferrer"&gt;shieldly.io/pricing&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>s3</category>
      <category>security</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Catching Risky IAM in CloudFormation Templates Before You Deploy</title>
      <dc:creator>Shieldly</dc:creator>
      <pubDate>Thu, 25 Jun 2026 12:23:09 +0000</pubDate>
      <link>https://dev.to/shieldlyio/catching-risky-iam-in-cloudformation-templates-before-you-deploy-mno</link>
      <guid>https://dev.to/shieldlyio/catching-risky-iam-in-cloudformation-templates-before-you-deploy-mno</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.shieldly.io/blog/" rel="noopener noreferrer"&gt;shieldly.io/blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most IAM security conversations center on the AWS console or raw JSON policy documents. But a significant share of production IAM misconfigurations originates in CloudFormation templates. By the time a CF stack is deployed, the IAM roles and policies inside it are live — and unless you reviewed the template before running &lt;code&gt;cdk deploy&lt;/code&gt; or &lt;code&gt;aws cloudformation create-stack&lt;/code&gt;, you may not have noticed what you just granted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why CloudFormation IAM Risk Is Different
&lt;/h2&gt;

&lt;p&gt;In a CloudFormation template, IAM roles are buried alongside compute resources, storage buckets, and networking config. Reviewers focused on architecture often skim past the &lt;code&gt;Properties.Policies&lt;/code&gt; block on a Lambda execution role.&lt;/p&gt;

&lt;p&gt;A single CloudFormation template may create five roles, two managed policy attachments, and three inline policies — all in a single deploy. The blast radius of an overly-permissive role is multiplied by every resource that role is attached to.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Most Common CF IAM Mistakes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;AdministratorAccess on Lambda execution roles.&lt;/strong&gt; Tempting during development. In production it grants &lt;code&gt;Action: *&lt;/code&gt; on &lt;code&gt;Resource: *&lt;/code&gt; to every invocation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# DO NOT SHIP THIS&lt;/span&gt;
&lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::IAM::Role&lt;/span&gt;
&lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ManagedPolicyArns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;arn:aws:iam::aws:policy/AdministratorAccess&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;Action: *&lt;/code&gt; in inline policies.&lt;/strong&gt; Inline policies defined inside an &lt;code&gt;AWS::IAM::Role&lt;/code&gt; resource are easy to overlook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Policies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;PolicyName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;InlinePolicy&lt;/span&gt;
    &lt;span class="na"&gt;PolicyDocument&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
          &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;       &lt;span class="c1"&gt;# Admin access, probably unintentional&lt;/span&gt;
          &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Missing permission boundaries on auto-created roles.&lt;/strong&gt; Without a boundary, any policy attached to the role later is unconstrained.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wildcard Resource on DynamoDB and S3.&lt;/strong&gt; Scoping actions correctly but leaving &lt;code&gt;Resource: *&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
    &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dynamodb:GetItem&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dynamodb:PutItem&lt;/span&gt;
    &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;   &lt;span class="c1"&gt;# applies to every table in the account&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fix: replace with the specific table ARN.&lt;/p&gt;

&lt;h2&gt;
  
  
  How CDK Makes It Easier to Slip Up
&lt;/h2&gt;

&lt;p&gt;CDK provides escape hatches — &lt;code&gt;addToRolePolicy&lt;/code&gt;, &lt;code&gt;addOverride&lt;/code&gt;, raw &lt;code&gt;PolicyStatement&lt;/code&gt; objects — that let developers bypass guardrails. A single escape-hatch call can inject an &lt;code&gt;Action: *&lt;/code&gt; statement into an otherwise well-scoped role, and nothing in the CDK build output will flag it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Catching It Before Deploy
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Analyze the synthesized template.&lt;/strong&gt; &lt;code&gt;cdk synth&lt;/code&gt; produces the CloudFormation JSON/YAML. Analyze that output before any deploy command runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Policy review gates in CI.&lt;/strong&gt; Add a template analysis step to your CI pipeline that runs on every PR touching infrastructure files. Fail the pipeline on HIGH or CRITICAL IAM findings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/shieldly.yml&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Analyze CloudFormation IAM&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shieldly-io/action@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cdk.out/**/*.template.json&lt;/span&gt;
    &lt;span class="na"&gt;fail-on-severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HIGH&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;SHIELDLY_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SHIELDLY_API_KEY }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;em&gt;Analyze CF templates automatically — paste a template into &lt;a href="https://www.shieldly.io/app/iam" rel="noopener noreferrer"&gt;Shieldly's free AI-Powered analysis&lt;/a&gt;. No signup, no credit card.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Launch offer: code &lt;code&gt;90Off2M&lt;/code&gt; — 90% off first 2 months. Pro from $1.90/mo. &lt;a href="https://www.shieldly.io/pricing" rel="noopener noreferrer"&gt;shieldly.io/pricing&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloudformation</category>
      <category>iac</category>
      <category>security</category>
    </item>
    <item>
      <title>AWS IAM NotAction vs Deny: The Misread Statement That Grants Everything</title>
      <dc:creator>Shieldly</dc:creator>
      <pubDate>Thu, 25 Jun 2026 11:57:10 +0000</pubDate>
      <link>https://dev.to/shieldlyio/aws-iam-notaction-vs-deny-the-misread-statement-that-grants-everything-3pl6</link>
      <guid>https://dev.to/shieldlyio/aws-iam-notaction-vs-deny-the-misread-statement-that-grants-everything-3pl6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.shieldly.io/blog/" rel="noopener noreferrer"&gt;shieldly.io/blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;NotAction&lt;/code&gt; is one of the most misunderstood IAM elements. Its name suggests denial but it does not deny anything. When paired with &lt;code&gt;Effect: Allow&lt;/code&gt;, it grants access to every action in all of AWS except the ones you listed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What NotAction Actually Means
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;NotAction&lt;/code&gt; inverts the &lt;code&gt;Action&lt;/code&gt; element. Instead of listing what a statement covers, you list what it &lt;strong&gt;excludes&lt;/strong&gt; — and the statement covers everything else.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"NotAction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"iam:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sts:*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows &lt;strong&gt;every action across every AWS service except IAM and STS&lt;/strong&gt;. That includes &lt;code&gt;s3:DeleteObject&lt;/code&gt;, &lt;code&gt;ec2:RunInstances&lt;/code&gt;, &lt;code&gt;rds:CreateDBSnapshot&lt;/code&gt;, &lt;code&gt;lambda:InvokeFunction&lt;/code&gt;, and everything else. The author almost certainly meant to restrict IAM access — they accidentally granted everything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  The One Legitimate Use Case
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;NotAction&lt;/code&gt; with &lt;code&gt;Effect: Deny&lt;/code&gt; is the one safe pattern. It is commonly used in Service Control Policies to create a "deny all except break-glass" control:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Deny"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"NotAction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"iam:CreateLoginProfile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"sts:AssumeRole"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"StringNotEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"aws:PrincipalArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::123456789012:role/BreakGlassRole"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This denies everything except the listed break-glass actions, for everyone except the break-glass role.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Rewrite the Dangerous Pattern
&lt;/h2&gt;

&lt;p&gt;If you wrote &lt;code&gt;NotAction&lt;/code&gt; + &lt;code&gt;Effect: Allow&lt;/code&gt; trying to deny specific actions, replace it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Instead of this (grants everything except IAM):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"NotAction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"iam:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Write this (allows only what you need):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::my-bucket/*"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add an explicit Deny for anything you want blocked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Deny"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"iam:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Effect: Deny&lt;/code&gt; with an explicit action list is not the same as &lt;code&gt;NotAction Allow&lt;/code&gt; — the former creates a hard deny, the latter creates a broad allow. They are opposite things.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Catch IAM statement mistakes automatically — paste a policy into &lt;a href="https://www.shieldly.io/app/iam" rel="noopener noreferrer"&gt;Shieldly's free AI-Powered analysis&lt;/a&gt;. No signup, no credit card.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Launch offer: code &lt;code&gt;90Off2M&lt;/code&gt; — 90% off first 2 months. Pro from $1.90/mo. &lt;a href="https://www.shieldly.io/pricing" rel="noopener noreferrer"&gt;shieldly.io/pricing&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>iam</category>
      <category>cloud</category>
    </item>
  </channel>
</rss>
