<?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>What the IAM Policy Simulator Actually Tells You (and What It Doesn't)</title>
      <dc:creator>Shieldly</dc:creator>
      <pubDate>Wed, 22 Jul 2026 13:11:41 +0000</pubDate>
      <link>https://dev.to/shieldlyio/what-the-iam-policy-simulator-actually-tells-you-and-what-it-doesnt-22ef</link>
      <guid>https://dev.to/shieldlyio/what-the-iam-policy-simulator-actually-tells-you-and-what-it-doesnt-22ef</guid>
      <description>&lt;p&gt;AWS ships a policy simulator: paste a principal, an action, a resource, and it tells you whether that specific request would be allowed or denied. It runs the same evaluation logic AWS uses at runtime, including identity policies, resource policies, permission boundaries, and SCPs. Teams reach for it before shipping a policy change, treat a clean result as a green light, and move on.&lt;/p&gt;

&lt;p&gt;That's a narrower guarantee than it looks like.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the Simulator Is Actually Answering
&lt;/h3&gt;

&lt;p&gt;The simulator answers one question at a time: given this exact principal, this exact action, this exact resource, what's the decision? That's genuinely useful — it's the same logic AWS applies at runtime, so a simulated "Allow" is a real allow, not a guess.&lt;/p&gt;

&lt;p&gt;But "is this one request permitted" and "is this policy safe to ship" are different questions. A policy can pass every simulated check you think to run and still grant far more than you intended, because the simulator only evaluates the specific combinations you feed it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where the Gap Shows Up
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;You have to already know what to ask.&lt;/strong&gt; The simulator doesn't enumerate what a principal can do — it confirms or denies whatever action/resource pair you type in. If you don't think to test &lt;code&gt;iam:PassRole&lt;/code&gt; against every role in the account, or &lt;code&gt;s3:GetObject&lt;/code&gt; against every bucket a wildcard resource might match, the simulator won't surface it for you. It's a lookup, not an audit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It doesn't follow chains.&lt;/strong&gt; Say the policy under test grants &lt;code&gt;sts:AssumeRole&lt;/code&gt; on a single role. The simulator will correctly tell you that's allowed. It won't tell you what that assumed role can then do, because that's a second policy, evaluated by a second simulator call you'd have to think to make. Multiply that by every &lt;code&gt;AssumeRole&lt;/code&gt; and &lt;code&gt;PassRole&lt;/code&gt; statement in a real account and the number of calls needed to actually cover the graph gets large fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wildcards pass silently.&lt;/strong&gt; &lt;code&gt;"Resource": "arn:aws:s3:::*/*"&lt;/code&gt; simulated against one specific bucket returns "Allow" — correctly. It doesn't flag that the same statement matches every other bucket in every other account the identity might reach. The simulator isn't wrong here; it's just not built to say "by the way, this also matches 40 other things you didn't ask about."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Boundaries and SCPs compute correctly, but only for the query you gave it.&lt;/strong&gt; If a permission boundary caps an identity policy down to two actions, the simulator will show that correctly for those two actions. It won't proactively tell you the boundary is even attached, or that removing it would change the outcome, unless you already knew to check.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Matters for Pre-Deploy Review
&lt;/h3&gt;

&lt;p&gt;None of this makes the simulator unreliable. The decision it returns for a given query is exactly what AWS would do at runtime. The failure mode is procedural: teams run a handful of simulated checks against the changes they're consciously aware of, get clean results, and treat that as equivalent to "this policy is safe." It answers the questions you ask. It doesn't tell you which questions you forgot to ask.&lt;/p&gt;

&lt;p&gt;That gap matters most on new roles and CI/CD-authored policies, where nobody has built the mental model of what the role should and shouldn't reach yet — there's no experienced reviewer's intuition to fill in the queries the simulator won't generate on its own.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Closes the Gap
&lt;/h3&gt;

&lt;p&gt;Closing it means generating the queries instead of hand-picking them: walking every &lt;code&gt;AssumeRole&lt;/code&gt; and &lt;code&gt;PassRole&lt;/code&gt; statement to see what's reachable two and three hops out, expanding wildcard resources against the actual resources in the account instead of one example, and checking whether a boundary or SCP is present at all before assuming the identity policy is the whole story. That's the same evaluation logic the simulator runs, just applied exhaustively instead of one query at a time.&lt;/p&gt;

&lt;p&gt;This is where AI-Powered analysis earns its keep: Shieldly's free demo at &lt;a href="https://shieldly.io/app/iam" rel="noopener noreferrer"&gt;shieldly.io/app/iam&lt;/a&gt; takes a pasted policy and runs that exhaustive pass automatically, surfacing over-privileged wildcards, chained AssumeRole/PassRole paths, and missing boundaries without requiring you to already know which combination to test. No signup needed for the demo.&lt;/p&gt;

&lt;p&gt;For teams: the CLI (&lt;code&gt;@shieldly/cli&lt;/code&gt;), GitHub Action, CDK Construct (&lt;code&gt;@shieldly/cdk-guard&lt;/code&gt;), and VS Code extension run the same analysis in CI and in your editor, so the check happens on every policy change, not just the ones someone remembers to simulate by hand. Team plan adds scheduled auto-scanning across a full account.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The policy simulator tells you the truth about the exact question you asked. The risk lives in the questions a wildcard, a chain, or an unfamiliar new role never prompted you to ask in the first place.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>iam</category>
      <category>devops</category>
    </item>
    <item>
      <title>Why Reviewing IAM Policies One at a Time Misses the Real Risk</title>
      <dc:creator>Shieldly</dc:creator>
      <pubDate>Tue, 21 Jul 2026 13:12:27 +0000</pubDate>
      <link>https://dev.to/shieldlyio/why-reviewing-iam-policies-one-at-a-time-misses-the-real-risk-4nh2</link>
      <guid>https://dev.to/shieldlyio/why-reviewing-iam-policies-one-at-a-time-misses-the-real-risk-4nh2</guid>
      <description>&lt;p&gt;Ask most teams how they review IAM, and the answer is some version of: pull up the policy, read the &lt;code&gt;Action&lt;/code&gt; and &lt;code&gt;Resource&lt;/code&gt; blocks, check for wildcards, move to the next policy. That catches the obvious problems. It misses the ones that matter most.&lt;/p&gt;

&lt;p&gt;The reason is simple: permissions don't live in one policy. They live in chains.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Policy That Looks Fine in Isolation
&lt;/h3&gt;

&lt;p&gt;Take a developer role with this 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="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;"arn:aws:iam::111111111111:role/deploy-pipeline"&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;Reviewed alone, this looks reasonable. The developer can't read S3, can't touch DynamoDB, can't do much of anything directly. &lt;code&gt;sts:AssumeRole&lt;/code&gt; on a single named role looks tightly scoped.&lt;/p&gt;

&lt;p&gt;Now look at what &lt;code&gt;deploy-pipeline&lt;/code&gt; can do:&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;"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="s2"&gt;"arn:aws:s3:::prod-customer-data/*"&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;The developer's own policy never mentions S3. But the developer can assume &lt;code&gt;deploy-pipeline&lt;/code&gt;, and &lt;code&gt;deploy-pipeline&lt;/code&gt; has full S3 access to a bucket holding customer data. Reviewed separately, both policies pass. Reviewed as a chain, the developer effectively has &lt;code&gt;s3:*&lt;/code&gt; on customer data, two hops away from anything their own policy admits to.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where Chains Actually Form
&lt;/h3&gt;

&lt;p&gt;A few patterns account for most of the chains we see:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Role assumption chains.&lt;/strong&gt; &lt;code&gt;sts:AssumeRole&lt;/code&gt; on Role A, and Role A can assume Role B, and Role B has the permissions that matter. Each hop looks narrow. The composite doesn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PassRole handoffs.&lt;/strong&gt; A CI/CD identity can't launch an EC2 instance directly, but it can pass a role to EC2 via &lt;code&gt;iam:PassRole&lt;/code&gt;, and that role can. The actual capability lives in the passed role, not the identity that triggered it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trust policy loops.&lt;/strong&gt; Role A trusts Role B's account, Role B trusts Role C's account, and nobody mapped the full trust graph across three AWS accounts. Each individual trust relationship was approved by someone. The transitive closure wasn't reviewed by anyone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resource policy plus identity policy.&lt;/strong&gt; An identity policy grants &lt;code&gt;s3:GetObject&lt;/code&gt; on &lt;code&gt;*&lt;/code&gt;. A bucket policy in a different account grants read access to that same identity by ARN. Neither policy alone looks broad. Together, the identity can read a bucket its own team never provisioned.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Manual Review Doesn't Catch This
&lt;/h3&gt;

&lt;p&gt;None of these chains are hidden exactly — they're spread across multiple documents that nobody reads side by side. A policy review checklist evaluates one JSON document against a rubric. It doesn't ask "what can this identity reach two or three hops out." That question requires building a graph: every role, every trust relationship, every &lt;code&gt;AssumeRole&lt;/code&gt; and &lt;code&gt;PassRole&lt;/code&gt; statement, resolved against every other policy in the account.&lt;/p&gt;

&lt;p&gt;At a handful of roles, a sharp reviewer can hold that graph in their head. At the hundreds of roles a mid-sized AWS account accumulates after a couple of years, nobody can. The review still happens — it's still a checkbox in the audit — it just stops catching the escalation paths that go more than one hop deep.&lt;/p&gt;

&lt;h3&gt;
  
  
  What to Actually Check
&lt;/h3&gt;

&lt;p&gt;If you're auditing IAM manually, a few questions catch a disproportionate share of chain risk:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For every &lt;code&gt;sts:AssumeRole&lt;/code&gt; statement, what can the assumed role do, and what can &lt;em&gt;it&lt;/em&gt; assume?&lt;/li&gt;
&lt;li&gt;For every &lt;code&gt;iam:PassRole&lt;/code&gt; statement, is it scoped with &lt;code&gt;iam:PassedToService&lt;/code&gt;, or can the role be passed to anything?&lt;/li&gt;
&lt;li&gt;Do any cross-account trust policies exist that nobody outside the original approver has looked at recently?&lt;/li&gt;
&lt;li&gt;Does any identity policy combine with a resource-based policy (S3 bucket policy, KMS key policy, Lambda resource policy) to grant more than either grants alone?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these require exotic tooling to check by hand for a small number of roles. They require deliberately looking one hop past the policy in front of you, which most reviews don't budget time for.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where Automation Actually Helps
&lt;/h3&gt;

&lt;p&gt;This is the specific problem AI-Powered analysis is well suited for: not spotting an obvious wildcard, but resolving a graph of AssumeRole and PassRole relationships across every policy in an account and flagging where the composite permission exceeds what any single policy discloses.&lt;/p&gt;

&lt;p&gt;Shieldly's free demo at &lt;a href="https://shieldly.io/app/iam" rel="noopener noreferrer"&gt;shieldly.io/app/iam&lt;/a&gt; traces trust relationships and PassRole targets when you paste a policy, so you can see what a role can reach beyond its own statements, not just what it explicitly grants. No signup needed for the demo.&lt;/p&gt;

&lt;p&gt;For teams: the CLI (&lt;code&gt;@shieldly/cli&lt;/code&gt;), GitHub Action, CDK Construct (&lt;code&gt;@shieldly/cdk-guard&lt;/code&gt;), and VS Code extension surface the same findings in CI and in your editor. Team plan adds scheduled auto-scanning across an account so chain risk gets caught as roles evolve, not just at the moment someone remembers to run an audit.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;A policy that passes review in isolation can still be one &lt;code&gt;AssumeRole&lt;/code&gt; away from a problem. The chain is the thing worth auditing, not just the link in front of you.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>iam</category>
      <category>devops</category>
    </item>
    <item>
      <title>5 IAM Condition Keys That Prevent the Mistakes Wildcards Create</title>
      <dc:creator>Shieldly</dc:creator>
      <pubDate>Mon, 20 Jul 2026 13:12:28 +0000</pubDate>
      <link>https://dev.to/shieldlyio/5-iam-condition-keys-that-prevent-the-mistakes-wildcards-create-20ok</link>
      <guid>https://dev.to/shieldlyio/5-iam-condition-keys-that-prevent-the-mistakes-wildcards-create-20ok</guid>
      <description>&lt;h2&gt;
  
  
  5 IAM Condition Keys That Prevent the Mistakes Wildcards Create
&lt;/h2&gt;

&lt;p&gt;An IAM policy with &lt;code&gt;Action: "s3:*"&lt;/code&gt; and &lt;code&gt;Resource: "*"&lt;/code&gt; is an obvious problem. You can spot it from across the room.&lt;/p&gt;

&lt;p&gt;The harder problems are the policies that look reasonable. &lt;code&gt;Action: "s3:GetObject"&lt;/code&gt; on &lt;code&gt;Resource: "*"&lt;/code&gt; — that's just one action, right? But it means the role can read objects from every S3 bucket in the account, including the one where CloudTrail logs land and the one the billing team uses for customer invoices.&lt;/p&gt;

&lt;p&gt;Condition keys are how you write policies that are broad where they need to be and tight everywhere else. They add a second dimension to every permission statement: not just &lt;em&gt;what&lt;/em&gt; this role can do, but &lt;em&gt;under what circumstances&lt;/em&gt; it can do it.&lt;/p&gt;

&lt;p&gt;Here are five condition keys that catch the most common IAM overpermission patterns.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. &lt;code&gt;aws:SourceIp&lt;/code&gt; — Restrict by Network Origin
&lt;/h3&gt;

&lt;p&gt;The most straightforward condition. If a role should only be used from your office VPN or a specific VPC, add an IP restriction.&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;"IpAddress"&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:SourceIp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10.0.0.0/8"&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 is useful for break-glass admin roles and any permission that should never be called from outside your network. Note that &lt;code&gt;aws:SourceIp&lt;/code&gt; checks the &lt;em&gt;original&lt;/em&gt; requester's IP — if the call comes through a VPC endpoint or AWS service, you might want &lt;code&gt;aws:VpcSourceIp&lt;/code&gt; instead.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. &lt;code&gt;aws:RequestedRegion&lt;/code&gt; — Lock to Specific Regions
&lt;/h3&gt;

&lt;p&gt;If your infrastructure only runs in us-east-1 and eu-west-1, a role that can create resources in ap-southeast-1 is a role you don't need. &lt;code&gt;aws:RequestedRegion&lt;/code&gt; prevents actions in regions you don't use, which limits blast radius if credentials leak.&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;"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:RequestedRegion"&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;"us-east-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eu-west-1"&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 is also a compliance win — many frameworks expect you to demonstrate that production workloads only run in approved regions.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. &lt;code&gt;iam:PassedToService&lt;/code&gt; — Scope PassRole
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;iam:PassRole&lt;/code&gt; is one of the most dangerous permissions in IAM. It controls which principals can pass which roles to which services. A wildcard &lt;code&gt;PassRole&lt;/code&gt; on &lt;code&gt;Resource: "*"&lt;/code&gt; means the principal can attach any role — including AdministratorAccess — to any service it can launch.&lt;/p&gt;

&lt;p&gt;Add &lt;code&gt;iam:PassedToService&lt;/code&gt; to restrict which AWS services can receive a passed role:&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;"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;"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="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;Now the principal can still pass roles, but only to Lambda. If it's a CI/CD role that deploys functions, that's exactly what you want. If the role gets compromised, the attacker can't use it to launch an EC2 instance with an admin instance profile.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. &lt;code&gt;aws:ResourceTag&lt;/code&gt; + &lt;code&gt;aws:PrincipalTag&lt;/code&gt; — Attribute-Based Access Control
&lt;/h3&gt;

&lt;p&gt;Tag-based conditions scale better than listing individual resource ARNs. Instead of maintaining a list of every EC2 instance a role can terminate, tag your instances by environment and write the condition once:&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;"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:ResourceTag/Environment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"staging"&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;Pair it with &lt;code&gt;aws:PrincipalTag&lt;/code&gt; on the role side, and you can write policies that say "users in the Engineering department can terminate instances tagged with Engineering." That's ABAC — attribute-based access control — and it's how teams with 200+ roles keep permissions manageable without drowning in policy length limits.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. &lt;code&gt;s3:ResourceAccount&lt;/code&gt; — Cross-Account S3 Access
&lt;/h3&gt;

&lt;p&gt;If a role in account A needs to read objects from a bucket in account B, &lt;code&gt;s3:GetObject&lt;/code&gt; on &lt;code&gt;Resource: "*"&lt;/code&gt; lets it read from &lt;em&gt;every&lt;/em&gt; account that grants it access — not just account B. An overly permissive bucket policy in a third account suddenly becomes readable.&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;"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;"s3:ResourceAccount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"222222222222"&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;Now the role can only access S3 objects in account B, even if other accounts have open bucket policies. This matters when you're managing cross-account access across an organization with dozens of accounts and hundreds of buckets.&lt;/p&gt;




&lt;h3&gt;
  
  
  Finding Policies That Miss These
&lt;/h3&gt;

&lt;p&gt;The problem with condition keys is that they're invisible until you go looking. An IAM policy with no conditions looks exactly as "valid" as one with them — both return &lt;code&gt;200 OK&lt;/code&gt; from the API, and neither throws an error until the wrong access happens.&lt;/p&gt;

&lt;p&gt;Shieldly's free AI-Powered demo at &lt;a href="https://shieldly.io/app/iam" rel="noopener noreferrer"&gt;shieldly.io/app/iam&lt;/a&gt; flags policies that are missing condition keys where they'd reduce risk. Paste a JSON policy and you'll see which statements would benefit from IP restrictions, PassedToService scoping, or resource tag conditions — no signup needed.&lt;/p&gt;

&lt;p&gt;For teams: the CLI (&lt;code&gt;@shieldly/cli&lt;/code&gt;), GitHub Action, CDK Construct (&lt;code&gt;@shieldly/cdk-guard&lt;/code&gt;), and VS Code extension all surface the same findings in your existing workflow.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Condition keys don't make your policies longer. They make them more precise. And precision is what separates "nobody's found it yet" from "there's nothing to find."&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>iam</category>
      <category>security</category>
      <category>devops</category>
    </item>
    <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>
  </channel>
</rss>
