Detect security posture drift in AWS CDK infrastructure using graph-based threat model analysis.
- The Problem
- What This Project Does
- Key Concepts
- How It Works
- Drift Type Taxonomy
- Project Structure
- Getting Started
- Roadmap
- Contributing
- References
Modern cloud infrastructure is defined as code (IaC). AWS CDK (Cloud Development Kit) lets you describe your entire architecture — networks, databases, APIs, IAM permissions — in Python or TypeScript, then compile it to a CloudFormation template that gets deployed.
Existing security tools for IaC (Checkov, CDK-nag, cfn-guard) are point-in-time scanners. They look at a single snapshot and flag known misconfigurations: an S3 bucket without encryption, an overly permissive IAM policy, a missing WAF rule.
What they cannot detect is security posture drift — the gradual, often invisible degradation of your architecture's security properties as the codebase evolves across commits:
- A new microservice quietly opens a network path from the internet to an internal database.
- A refactor moves a service outside its original VPC trust boundary without adding a compensating control.
- A third-party API integration is added, creating a new data egress path for PII that no scanner flagged because each individual resource looks fine in isolation.
This problem is getting worse with AI-assisted development. LLM-generated CDK code is syntactically correct and passes linters, but can introduce subtle architectural changes that only become visible when you compare the threat model of the old version to the new one.
There is currently no tool that computes a semantic diff of the threat model between two versions of a CDK application.
ctm takes two versions of a CDK app (a commit pair T1 → T2), converts each into a graph intermediate representation, and computes a ΔTM (delta threat model) — a structured report of which security properties changed and whether those changes are regressions, improvements, or neutral refactors.
At a high level:
- Run
cdk synthon both versions to get CloudFormation JSON. - Parse each template into a graph (CFG + DFG) that captures resource relationships and data flow paths.
- Annotate graph edges with security properties: is this path encrypted? authenticated? publicly reachable?
- Run taint analysis: trace paths from sensitive data sources (secrets, PII stores) through the graph to reachable sinks.
- Generate a structured threat model from the graph: identify trust boundaries, processes, data flows, and data stores, then apply STRIDE to produce a set of threats.
- Serialize the annotated graph + threat model as a versioned snapshot (T1, T2).
- Diff the two snapshots: what flows, boundaries, or threats were added, removed, or changed?
- Classify each change against a taxonomy of security drift types and assign a verdict.
The output is a DriftReport — machine-readable, reviewable by humans, and designed to be gated in CI/CD.
If you're new to any of these ideas, read this section first. If you're already familiar, skip ahead to How It Works.
Infrastructure as Code means your cloud resources — servers, databases, networking, permissions — are declared in source files rather than clicked through a UI. AWS CDK (Cloud Development Kit) is a framework that lets you write this in Python/TypeScript/Java, then compiles it to a CloudFormation template (a JSON/YAML file) that AWS uses to provision the actual resources.
When we say "CDK app", we mean the source code. When we say "synth output" or "template", we mean the compiled JSON.
Threat modeling is a structured way to answer: what can go wrong with this system, and how bad would it be?
You map out your architecture — what components exist, how they communicate, what data they hold — and then systematically reason about attackers: what paths could they take? What data could they reach? What controls are in place?
The standard framework is STRIDE (Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, Elevation of Privilege).
Traditional threat modeling happens once, at design time. Continuous Threat Modeling means re-evaluating the threat model automatically every time the architecture changes — treating it like a test that runs in CI.
The challenge is that threat modeling is expensive when done manually, and existing automated tools are either too shallow (point-in-time linters) or too slow (full LLM-based analysis on every commit).
ctm addresses this by making the threat model diffable: instead of re-running a full analysis, compute what changed and whether those changes are security-relevant.
Security posture drift is when a system's security properties degrade over time through a series of small, individually innocuous changes — none of which would trigger an alert on their own.
Example: over three commits, a Lambda function gains a new IAM permission (commit 1), that function gets wired to a new public API endpoint (commit 2), and the endpoint loses its authentication requirement during a refactor (commit 3). No single commit is obviously wrong. The combination creates an unauthenticated internet path to a privileged Lambda.
ctm is designed to catch this class of drift.
These are two different graph representations of your infrastructure. A useful analogy: think of a house.
CFG — Control Flow Graph (in IaC: provisioning dependency graph)
"What has to be built before what?"
Nodes are CloudFormation resources. Edges represent Ref, GetAtt, and DependsOn relationships — resource A cannot be created until resource B exists.
This is the skeleton of your infrastructure. It's derived directly from the template's structure, without knowing anything about runtime behavior.
Example edge: LambdaFunction → IAMRole (the Lambda declares a Ref to the role, so the role must be provisioned first).
DFG — Data Flow Graph (runtime data movement)
"What data can actually reach what, at runtime?"
Nodes are data-bearing resources (EC2, Lambda, S3, RDS, API Gateway…) plus a special Internet entry node. Edges represent real runtime data paths: HTTP traffic through CloudFront/ALB/EC2 chains, IAM-mediated access (Lambda → S3 via a role policy), SG ingress rules.
Each edge carries security annotations: encrypted: bool, authenticated: bool, public_facing: bool, protocol.
This is the blood flow of your infrastructure. Taint analysis and reachability analysis run on the DFG, not the CFG.
CFG (skeleton): LambdaFunction --[Ref]--> IAMRole --[Ref]--> S3Bucket
DFG (blood flow): Internet --> APIGateway --> LambdaFunction --> S3Bucket
(IAM-permitted, encrypted=true)
The CFG tells you the template is valid. The DFG tells you an unauthenticated internet request can eventually read from S3.
Taint analysis answers: if this data is sensitive, where can it end up?
You mark certain resources as taint sources — things that hold sensitive data: Secrets Manager, SSM Parameter Store, RDS databases, S3 buckets with PII. You then propagate taint labels forward through DFG edges. Wherever taint reaches a sink (a public endpoint, an external API, an unencrypted log), you record a potential violation.
Once the DFG is built and taint analysis is complete, ctm lifts the graph into standard threat modeling primitives — the same building blocks a security engineer would draw on a whiteboard:
| Primitive | Derived from |
|---|---|
| Trust boundaries | VPC boundaries, subnet partitions, the Internet entry node — anywhere data crosses a security perimeter |
| Processes | Compute resources that transform or route data: Lambda functions, EC2 instances, ECS tasks, API Gateway |
| Data flows | DFG edges between components, annotated with encryption, authentication, and protocol |
| Data stores | Persistent storage resources: RDS, DynamoDB, S3, ElastiCache, Secrets Manager |
| External entities | The Internet node and any third-party integrations |
From these primitives, the threat model generator applies STRIDE to each element:
- Spoofing — can an attacker impersonate a process or external entity? (e.g. unauthenticated API endpoint)
- Tampering — can data be modified in transit or at rest? (e.g. unencrypted data flow)
- Repudiation — are actions logged and attributable? (e.g. missing CloudTrail coverage)
- Information Disclosure — can sensitive data be read by an unauthorized party? (e.g. taint reaching a public sink)
- Denial of Service — can availability be disrupted? (e.g. no WAF, no rate limiting)
- Elevation of Privilege — can a component gain more access than intended? (e.g. privilege drift in IAM)
The output of this step is a structured ThreatModel — a list of threats, each tied to a specific component or data flow in the graph. This becomes part of the versioned snapshot, so the ΔTM diff can detect when new threats appear or existing ones are resolved.
The ΔTM is the core output of ctm. It's the diff between the threat model at T1 and the threat model at T2 — expressed as structured data:
- Added flows: new data paths that didn't exist before
- Removed flows: paths that were deleted (may be improvements or regressions)
- Changed properties: a path that existed before but changed (e.g.
encryptedflipped fromtruetofalse) - Drift type: which category of security drift this represents
- Verdict:
Block/Warn/Approve/Needs Review
T1: cdk synth → cfn_t1.json T2: cdk synth → cfn_t2.json
\ /
↓ ↓
[Parser] [Parser]
CfnTemplate {resources, deps} CfnTemplate {resources, deps}
| |
[CFG Builder] [CFG Builder]
Provisioning dependency graph Provisioning dependency graph
| |
[DFG Builder] [DFG Builder]
Runtime data flow graph Runtime data flow graph
(network paths + IAM edges, (network paths + IAM edges,
annotated: encrypted/auth) annotated: encrypted/auth)
| |
[Taint Analysis] [Taint Analysis]
Propagate sensitivity labels Propagate sensitivity labels
Flag unsafe source → sink paths Flag unsafe source → sink paths
| |
[Threat Model Generator] [Threat Model Generator]
Trust boundaries, processes, Trust boundaries, processes,
data flows, data stores data flows, data stores
Apply STRIDE → ThreatModel Apply STRIDE → ThreatModel
| |
[Serializer] [Serializer]
snapshot_t1.json snapshot_t2.json
{graph, threats, taint_violations} {graph, threats, taint_violations}
\ /
↓ ↓
[ΔTM Diff Engine]
Compare: added/removed flows, boundaries,
threats, changed edge properties
|
[Drift Classifier]
Map diffs → drift types → verdicts
|
DriftReport.json
Not all architectural changes are equal. ctm classifies changes against a taxonomy of security drift types, each with an expected verdict.
| Drift Type | What it means | Verdict |
|---|---|---|
| Architectural Reachability Drift | A new network path exists from Internet to a resource that was previously unreachable |
Block / Warn |
| Trust Boundary Drift | A new data flow crosses a VPC or security boundary without a compensating control (gateway, auth layer) | Block / Warn |
| Dependency / Integration Drift | A new external service (third-party API, SaaS) is wired into the architecture | Warn |
| Drift Type | What it means |
|---|---|
| Exposure Drift | A previously internal resource becomes internet-facing |
| Privilege Drift | A service gains broader IAM permissions than before |
| Encryption Drift | Encryption at rest or in transit is removed or downgraded |
| Authentication Drift | A public endpoint loses its authentication requirement |
| Authorization Drift | Authenticated users gain broader access than before |
| Egress Drift | A workload gains new outbound internet access |
| Secrets Management Drift | A hardcoded credential is introduced, or a secret is moved out of a managed store |
| Logging / Monitoring Drift | Audit logging or CloudTrail coverage is removed |
Each drift type also has a no-material-change example (a refactor that looks like drift but isn't) and an improvement example (a change that reduces risk). The classifier distinguishes all three.
ctm/
├── ctm-iac/ # Core Python package — CDK IaC analysis engine
│ ├── main.py # Entry point (placeholder)
│ └── pyproject.toml
└── README.md
As development progresses, modules will be added under ctm-iac/:
Prerequisites
- Python 3.13+
- uv (package manager)
- AWS CDK CLI (
npm install -g aws-cdk)
Install
git clone https://github.com/Agentic-AI-Risk-Mitigation/ctm.git
cd ctm
uv pip install -e ctm-iac/- CloudFormation template parser
- CFG construction (provisioning dependency graph)
- DFG construction (runtime data flow graph, network + IAM paths)
- Resource taxonomy (AWS resource type → category classification)
- Taint propagation engine
- Threat model generator (trust boundaries, processes, data flows, stores → STRIDE threats)
- Graph + threat model serialization (T1 / T2 snapshots)
- ΔTM diff algorithm
- Drift type classifier — P1 types (Reachability, Trust Boundary, Dependency)
- Drift type classifier — P2 types (Exposure, Privilege, Encryption, etc.)
- Dataset of real CDK applications with annotated T1/T2 drift pairs
- CLI interface
- CI/CD integration (GitHub Action)
This project is in early development.
- Read the Key Concepts section above — make sure you're comfortable with CFG vs DFG and what ΔTM means.
- Check the Roadmap for what's in progress.
- The
ctm-iac/package is where all algorithm code lives. - Open an issue before starting a significant new module so the team can align on interface design.
- ACSE-Eval: "Can LLMs threat model real-world cloud infrastructure?" — arxiv 2505.11565
- OWASP Threat Modeling
- AWS CDK Documentation
- Checkov — point-in-time IaC scanner (prior art)
- CDK-nag — CDK-specific rule-based linter (prior art)
MIT — see LICENSE.