Picture the scene: a younger me, sitting at my desk, trying to harden an S3 bucket against accidental deletion. Proper grown-up engineering. The bucket policy I wrote did its job extremely well. It denied everything I'd told it to deny, and it denied it to absolutely everyone, because I'd written the Deny statement without leaving a carveout for the bucket owner. No NotPrincipal. No aws:PrincipalArn exception for the account root. Just a wall.
What followed was a special kind of slow-motion realisation that goes something like this:
- "Huh, that's odd."
- "No, surely the admin role can override this."
- "Oh."
Spoiler: the admin role could not, in fact, override that.
You're probably sitting there either with your head in your hands, questioning why you'd read anything I write from now on, or thinking: "Ah, but Lee, surely AWS has some way around that?" Well, actually, no easy one. I raised an AWS Support ticket sheepishly, but there was no magic unlock button waiting for me. I'd accidentally welded my own bucket shut. I did end up fixing it in the end, but I think my brain has trauma-blocked the eventual fix.
That moment, plus a few like it, is where CDK Insights eventually came from. But it took a while to get there.
The thing IAM does to you
If you've spent any real time writing CDK, you've probably seen the wildcard version of this story. Someone - possibly you, definitely me, no judgement here - attaches a Lambda role with actions: ['s3:GetObject'] and resources: ['*'], and it sails through review on a Friday afternoon because the diff is forty lines and the wildcard is on line 23.
That's the over-permissive flavour. It's the one most security tools in the ecosystem are tuned to catch. Checkov flags it. cfn-lint flags it. cdk-nag flags it as AwsSolutions-IAM5.
The lockout I built is the inverse. It's IAM going wrong in the opposite direction — too tight rather than too loose. IAM mistakes are not always "whoops, everyone can access everything." Sometimes they are "congratulations, no one can access anything, including you."
And here's the slightly awkward bit: this inverse craziness seems to slip through the CDK-focused static-analysis tools I checked while writing this post. From my experience, most of the tooling is looking for overly broad access. This was the opposite problem. Or maybe they're using the telescope correctly and IAM* the one holding the telescope upside down. Either way, the bucket stayed locked.
*Lee Priest and Instance Labs Ltd accept no liability for injuries sustained while reacting to terrible puns.
Both mistakes - the wildcard and the wall - come from the same place. They come from writing IAM faster than you understand IAM. Which, in fairness, is most of us most of the time. We're not bad engineers; we just have a sprint deadline and a ticket to close.
And here's the part that nagged at me: even when a tool does flag the problem, "wildcard detected" or "Deny without carveout" tells you the what, not the why. A junior engineer reads the warning and either suppresses it because they don't see the blast radius or panics and rewrites the whole stack. Neither is the response you actually want. The response you want is something more like:
"This would let your Lambda read every bucket in the account, including the one with the customer-data backups. Here's how to scope it."
That's a teaching response, not a flagging response. And that gap — between catching a problem and explaining a problem — was the seed of the whole thing.
What's already out there
Before we go any further, let's give credit where it's due. There are some genuinely good tools in this space already:
-
Checkov - broad IaC scanner that supports CloudFormation (among many other formats). Has a "CDK support" page that essentially boils down to: run
cdk synth, then point Checkov at the resulting.template.json. - cfn-lint - AWS's own CloudFormation linter. Brilliant at what it does. Pure CFN.
- cfn_nag - Stelligent's Ruby-based CFN scanner. Veteran of the space.
- cdk-nag - and this one's different. cdk-nag is a CDK Aspect, so it actually walks the construct tree at synth time. Same lifecycle stage as CDK Insights, with a different rule library. We integrate with it.
The first three all share a property that started to bother me the more I used them. They scan the synthesised CloudFormation as a free-standing artefact, with no first-class link back to the construct tree that produced it. By the time a finding comes back, you're staring at line 4,287 of MyStack.template.json and trying to reverse-engineer which bit of TypeScript caused it. Workable for a tiny stack, miserable for a real one.
Which is fair enough - those tools weren't built for CDK specifically. They were built for CloudFormation, and CDK is one of many ways CloudFormation gets generated. But if you're a CDK developer, the bit you actually want — show me the line of TypeScript that did this - is missing.
Static analysis, but built around the construct tree
Here's the thing I want to be honest about: CDK Insights also analyses synthesised CloudFormation at the rules layer. It has to. The static checks operate on CloudFormationStack objects because that's what cdk synth produces, and that's where the resource definitions actually live.
The difference is what we do with the metadata that CDK itself embeds in the synth output, which the other CFN-only tools mostly ignore:
-
aws:cdk:pathmetadata. CDK quietly tags every resource with the construct-tree path that produced it. We use that to map findings back to constructs. -
Per-construct stack traces. When
@aws-cdk/core:stackTraceis enabled (whichcdk-insights setupwrites into yourcdk.jsonfor you), CDK captures the source line that produced each construct. That's what powers our file-and-line attribution. -
Construct-tree walking via the Aspect. When you use
createCdkInsightsAspect(), we operate before synth on actualConstructobjects, which is the only path that lets us applyNagSuppressionsbased on construct identity. -
CDK-native suppressions.
.cdk-insights.jsonignorePathsworks on construct paths, andValidations.of(scope).acknowledge(...)is a CDK-first API that has no analogue in the CFN-only world.
So findings can point to line 23 of lib/ingest-stack.ts rather than line 4,287 of a generated YAML file. That single feature has done more to make findings actionable than any individual rule I've added.
The first version of the rule library had maybe twenty checks. As of the time of writing, the registry has over a hundred rules across more than thirty AWS services, with compliance coverage hitting 80% of SOC 2 controls and 79% of NIST 800-53.
The Bedrock thing
I'll be honest: the other thing pushing me towards building this was wanting to use AWS Bedrock for something that wasn't generating energy drink memes.
This was early in the current AI cycle, and most of the Bedrock demos I was seeing were either chatbot wrappers or party tricks. The thing I actually wanted - for me, anyway - was an AI layer that did real work. And the obvious pairing was:
- Static rules tell you what is wrong. They're mechanical. Deterministic. Fast.
- A model with the right context tells you why it matters in your specific stack, and what the right next step is.
That's the AI tier in CDK Insights. The static analysis runs locally and is free, forever — no signup, no card, no telemetry. The AI insights are opt-in: free accounts get 500 credits a month, more on paid tiers. Model choice now spans Amazon Nova Lite, Mistral 14B, Claude Haiku 4.5, and Claude Sonnet 4.6, all routed through Bedrock.
🪣 Did you know: the first 50 readers can get a free month by using code
IMMORTALBUCKETat checkout?
The model isn't doing the analysis. The rules are doing the analysis. The model is the bit that makes the analysis legible to the developer reading it. That's a distinction I keep wanting to underline because the AI conversation tends to flatten it.
What it actually looks like to use
If you're a CDK developer reading this and wondering what the onboarding looks like, I tried to make it as boring as possible:
npx cdk-insights scan
That's the whole thing. It synthesises your stacks, runs the rule pack, and gives you a table of findings with file and line attribution. Output options:
- Table — for when you're at the terminal eyeballing things.
- JSON — for CI pipelines.
- Markdown — for sharing with the team.
- SARIF — for uploading to GitHub Code Scanning.
There's a GitHub Action - instancelabs/cdk-insights-action@v1 - that posts findings as PR comments and supports merge gates by severity. The Aspect and the Validations Plugin are there for projects that want synth-time enforcement rather than a separate scan step.
The thing I'm probably proudest of is the smallest: as mentioned earlier, the source-location attribution. It's the bit that's saved me the most "where the heck is this resource defined?" moments in my own dogfooding.
A bit about how it gets built
Quick aside: CDK Insights is eleven repos. The CLI, the GitHub Action, an event-driven AWS backend (EventBridge bus, DynamoDB knowledge base, Bedrock-backed AI tier, Cognito, Stripe), a Next.js marketing-and-billing site, all the supporting infrastructure. I built it on my own, alongside everything else that goes with running a one-person company in the UK.
I'm going to be straight with you: I would not have shipped this as a solo founder five years ago. The product is too broad. There are too many moving parts.
What changed is that I'm not the one holding all of it any more. I work with agentic AI tooling for most of the actual writing, and my job has shifted to introduce a secondary role closer to chief reviewer. I write the spec, I argue with the model about the right shape of an API, I look at the diff, and I push back when the suppression heuristic is too aggressive. The pace it produces is, honestly, a bit weird and at times scary. I find myself having to rein myself in sometimes and not get carried away with a "ship all the things!" mindset.
But this isn't a post about agentic building, so I'll spare you the full essay. Just wanted to be upfront about it, because the alternative is pretending I built all this with my bare hands and a 24-pack of energy drinks, which would be insulting to your intelligence. And let's face it, pretty much everyone is using some flavour of LLM somewhere, be it Claude, Copilot, ChatGPT or something else.
Plot twist: my own tool wouldn't have caught the lockout
Right. Here's the bit I didn't expect to be writing.
Halfway through drafting this post — somewhere around the paragraph above where I was being smug about purpose-built CDK static analysis - I had one of those moments. Hang on. Would CDK Insights actually have caught the lockout I opened with?
I went and looked.
There's a check in the codebase called broadPrincipalPoliciesCheck that scans S3, SQS, SNS, and KMS resource policies for Principal: "*". It's a good check. It catches the over-permissive direction cleanly. And right at the top of the loop, there's this comment:
// Only check Allow statements — Deny with broad principal is typically intentional
if (effect !== 'Allow') continue;
That comment is true. Most Deny statements with broad principals are intentional - that's how you write the SSL-only enforcement that cdk-nag actively requires. But it also means the lockout pattern — the exact one that gave me the original idea — slides past the rule untouched.
The tool I built in 2026 would not have caught the bug from years ago that gave me the idea for the tool.
There is a slightly humbling laugh in there somewhere.
So I went and built the rule
The good news is that this is exactly the kind of feedback the rule registry is supposed to absorb. So before publishing this post, I added a new rule: s3-bucket-policy-self-lockout.
What it flags:
-
AWS::S3::BucketPolicyresources containing aDenystatement that targetss3:DeleteBucket,s3:*, or*actions, where thePrincipalis broad and there's noNotPrincipalcarveout oraws:PrincipalArnexception for the account root or admin role.
What it doesn't flag (because false positives are the death of this kind of rule):
- The SSL-only
Denypattern (aws:SecureTransport=false). cdk-nag actively wants you to have one of these, and so do we. - Denies with explicit
NotPrincipalexceptions — you've already left yourself a key. - Denies whose
Resourcescope doesn't actually cover the bucket itself.
Severity: CRITICAL. Recovery from this one isn't a hotfix; it's an AWS Support case.
There's something quite funny about a product whose origin story exposes a gap in the product itself. It's also exactly how the rule library is meant to grow. I get told about a new mistake, or I make a new mistake, I add a rule, and the next person doesn't make the same mistake. The library is alive, not finished.
If you've made an IAM mistake that no scanner caught, I'd genuinely love to hear about it. There's a decent chance it's a rule I haven't written yet.
Where it goes from here
The compliance framework backfill landed earlier this week, which lifts SOC 2 coverage to 80% and NIST to 79%. That unlocks a thing I've wanted to ship for a while — the ability to look at your stack through the lens of your auditor's framework, not a generic security rulebook.
The AI tier is going to keep getting more useful. The models keep getting better, and the things I want to do with the output — cross-stack analysis, what-if scenarios, suggested remediations as actual diffs — are all on the roadmap.
If you build with CDK and want to give it a go, the static analysis is free forever and one command away:
npx cdk-insights scan
If you find a rule that's wrong, tell me. If you find a rule I should have, tell me that too — apparently, sometimes I find them while writing blog posts.
Let's mark this post as resolved
Thanks for sticking with me through this one. It started as "I'll write a quick post about why I built the thing" and ended up being a slightly more honest piece about how the thing keeps building itself in response to use.
Which, on reflection, is probably the most accurate way to describe what shipping a product feels like in 2026.
If you want to try CDK Insights:
- 🌐 Site: cdkinsights.dev
- 📦 npm:
npx cdk-insights scan - ⚙️ GitHub Action:
instancelabs/cdk-insights-action@v1 - 🪣 First 50 readers: free month with code
IMMORTALBUCKETat checkout
I genuinely hope it helps people working with the AWS CDK, be they junior devs just starting out or wise CDK gurus who pushed maybe a couple of lines too early.



Top comments (0)