Static IaC threat modeler that parses Terraform, CloudFormation, and Kubernetes manifests and produces a structured STRIDE threat model report with a data flow diagram. No network calls, no cloud credentials, fully offline.
pip install threatmap
# Run as a command
threatmap scan ./examples --output report.md --fail-on HIGH
# Or as a module
python -m threatmap scan ./examples --ascii| Format | Provider | Extension |
|---|---|---|
| Terraform HCL | AWS, Azure, GCP | .tf |
| CloudFormation | AWS | .yaml, .yml, .json |
| Kubernetes manifests | Kubernetes | .yaml, .yml |
Install from PyPI:
pip install threatmapOr for local development:
git clone https://github.com/bogdanticu88/threatmap.git
cd threatmap
pip install -e .Scan a directory and print a Markdown report to stdout:
threatmap scan ./terraform/Scan multiple paths and write a JSON report to a file:
threatmap scan ./terraform/ ./k8s/ ./cloudformation/ --format json --output report.jsonGenerate an interactive HTML report or a SARIF report for GitHub Security:
threatmap scan ./infra/ --format html --output report.html
threatmap scan ./infra/ --format sarif --output report.sarifCI gate β exit code 1 if any CRITICAL or HIGH threat is found:
threatmap scan ./infra/ --fail-on HIGH --output threat-report.mdPrint a terminal summary table only, without writing a full report:
threatmap scan ./infra/ --summaryUse ASCII-only severity indicators (no emojis) for environments that don't support Unicode:
threatmap scan ./infra/ --ascii --output report.mdRunning threatmap scan ./examples --output report.md against the bundled examples produces a full Markdown report. Below is a representative excerpt.
| ID | Severity | STRIDE Category | Resource | Description |
|---|---|---|---|---|
| T-001 | π΄ CRITICAL | Information Disclosure | AuditBucket |
S3 bucket 'AuditBucket' has no public access block configured β bucket may be publicly accessible. |
| T-002 | π΄ CRITICAL | Spoofing | WebSecurityGroup |
Security group 'WebSecurityGroup' exposes SSH/RDP (port 22/3389) to 0.0.0.0/0. |
| T-003 | π΄ CRITICAL | Elevation of Privilege | app_contributor |
Role assignment 'app_contributor' grants the privileged role 'Contributor'. |
| T-006 | π HIGH | Information Disclosure | AuditBucket |
S3 bucket 'AuditBucket' does not have server-side encryption configured. |
| T-008 | π HIGH | Elevation of Privilege | api |
Container 'api' in Deployment 'api' may run as root (no runAsNonRoot=true or runAsUser=0). |
| T-011 | π HIGH | Elevation of Privilege | web |
EC2 instance 'web' allows IMDSv1 β metadata service accessible without session tokens, enabling SSRF-based credential theft. |
### T-002 β Spoofing (CRITICAL)
Resource: AWS::EC2::SecurityGroup.WebSecurityGroup
Property: ingress.ssh_rdp_open
Finding: Security group 'WebSecurityGroup' exposes SSH/RDP (port 22/3389) to 0.0.0.0/0.
Mitigation: Remove public SSH/RDP access. Use AWS Systems Manager Session Manager
or a bastion host with IP restrictions.
The report appends a Mermaid flowchart LR diagram. Nodes are coloured by worst-case severity (π΄ red = CRITICAL, π orange = HIGH). Paste the block into any Mermaid renderer or view it directly on GitHub.
flowchart LR
Internet((Internet))
subgraph Networking
aws_security_group_web_sg{web_sg}
NetworkPolicy_default_deny{default-deny}
azurerm_network_security_group_app_nsg{app_nsg}
end
subgraph Compute
aws_instance_web[web]
end
subgraph Kubernetes
Namespace_myapp[myapp]
Deployment_api[api]
Service_api_svc[api-svc]
Ingress_api_ingress[api-ingress]
end
subgraph Data
aws_s3_bucket_app_data[(app_data)]
aws_db_instance_app_db[(app_db)]
azurerm_storage_account_app_storage[(app_storage)]
end
subgraph Security
azurerm_key_vault_app_kv[app_kv]
end
subgraph Identity
azurerm_role_assignment_app_contributor[/app_contributor/]
end
AWS__S3__Bucket_AppBucket -->|ref| AWS__S3__Bucket_AuditBucket
AWS__CloudTrail__Trail_AppTrail -->|ref| AWS__S3__Bucket_AuditBucket
Internet -->|HTTPS| Ingress_api_ingress
style aws_security_group_web_sg fill:#ff4444,color:#fff
style aws_s3_bucket_app_data fill:#ff4444,color:#fff
style aws_instance_web fill:#ff8800,color:#fff
style Deployment_api fill:#ff8800,color:#fff
style azurerm_key_vault_app_kv fill:#ffcc00,color:#000
style azurerm_network_security_group_app_nsg fill:#ff8800,color:#fff
style azurerm_role_assignment_app_contributor fill:#ff4444,color:#fff
threatmap now includes Graph Intelligence that traces relationships between resources. It automatically identifies "chained" threats where a compromise of one resource (e.g., an internet-exposed EC2) leads directly to another (e.g., a private S3 bucket), flagging these as Elevation of Privilege attack paths.
You can define internal security requirements by creating a threatmap_rules.yaml in your project root.
rules:
- resource_type: "aws_s3_bucket"
property: "force_destroy"
expected: false
stride: "Tampering"
severity: "MEDIUM"
description: "Production buckets should not have force_destroy enabled."
mitigation: "Set force_destroy = false."Most findings now include a remediation field (visible in JSON, HTML, and SARIF reports) that provides the exact code snippet needed to fix the security issue.
Each cloud provider has its own analyzer module:
threatmap/analyzers/
βββ aws.py # 22 rules β S3, IAM, EC2, RDS, EKS, CloudTrail, KMS, Lambda
βββ azure.py # 19 rules β Storage, Key Vault, NSG, RBAC, AKS, ACR, SQL
βββ gcp.py # 15 rules β GCS, Firewall, Compute, Cloud SQL, GKE, IAM, KMS
βββ kubernetes.py # 17 rules β workloads, RBAC, network, secrets
Each rule is a function that receives a Resource object (normalised from whatever source format was parsed) and returns a Threat if the condition is met. Rules are plain Python conditionals β no DSL, no regex engine, no external ruleset files.
Severity reflects both exploitability and blast radius:
| Severity | Meaning |
|---|---|
| CRITICAL | Directly exploitable with no additional preconditions (e.g. SSH open to 0.0.0.0/0, wildcard IAM policy, cluster-admin binding to anonymous) |
| HIGH | Significant risk requiring one additional step (e.g. unencrypted RDS with public access, IMDSv1 on an EC2 instance) |
| MEDIUM | Defence-in-depth controls missing β lower immediate risk but violates security baselines (e.g. no versioning, no logging, no resource limits) |
| LOW | Best-practice gaps with limited standalone exploitability (e.g. Lambda not in VPC) |
- No heuristics or ML β every rule fires on a concrete, unambiguous property value (e.g.
publicly_accessible = true,Principal: "*"). - Conservative defaults β if a property is absent, the rule assumes the insecure default (e.g. no
metadata_optionsblock on an EC2 instance means IMDSv1 is active, because that is AWS's default). - No cross-account or runtime state β the tool only looks at what is declared in the template. It does not attempt to infer what SCPs, permission boundaries, or runtime configs might mitigate a finding.
- Deduplication in the engine β findings are keyed on
(stride_category, resource_name, trigger_property)so the same logical issue is never reported twice even if it appears across multiple file formats.
# .github/workflows/threat-model.yml
name: Threat Model
on: [pull_request]
jobs:
threatmap:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install threatmap
run: pip install threatmap
- name: Run threat model scan
run: |
threatmap scan ./infra/ \
--format markdown \
--output threat-report.md \
--fail-on HIGH
- name: Upload threat report
if: always()
uses: actions/upload-artifact@v4
with:
name: threat-report
path: threat-report.mdThe --fail-on HIGH flag makes the job exit with code 1 if any HIGH or CRITICAL threat is found, blocking the PR merge. The uploaded artifact gives reviewers the full report without leaving the pull request.
| Provider | Rules |
|---|---|
| AWS (Terraform + CloudFormation) | 22 |
| Azure (Terraform) | 19 |
| GCP (Terraform) | 15 |
| Kubernetes | 17 |
| Total | 73 |
Categories covered per provider:
| Provider | S | T | R | I | D | E |
|---|---|---|---|---|---|---|
| AWS | β | β | β | β | β | β |
| Azure | β | β | β | β | β | β |
| GCP | β | β | β | β | β | β |
| Kubernetes | β | β | β | β | β | β |
(S=Spoofing, T=Tampering, R=Repudiation, I=Information Disclosure, D=Denial of Service, E=Elevation of Privilege)
Run tests:
pytest tests/ -vRun with coverage:
pytest tests/ --cov=threatmap --cov-report=term-missing- Fork the repository
- Add rules in
threatmap/analyzers/<provider>.pyfollowing the existing pattern - Add a fixture in
tests/fixtures/that triggers the new rule - Add assertions in
tests/test_analyzers.py - Open a pull request