Static IaC threat modeler using STRIDE
Project description
threatmap
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.
Quick Start
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
Supported Formats and Providers
| Format | Provider | Extension |
|---|---|---|
| Terraform HCL | AWS, Azure, GCP | .tf |
| CloudFormation | AWS | .yaml, .yml, .json |
| Kubernetes manifests | Kubernetes | .yaml, .yml |
Install
Install from PyPI:
pip install threatmap
Or for local development:
git clone https://github.com/bogdanticu88/threatmap.git
cd threatmap
pip install -e .
Usage
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.json
Generate 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.sarif
CI gate โ exit code 1 if any CRITICAL or HIGH threat is found:
threatmap scan ./infra/ --fail-on HIGH --output threat-report.md
Print a terminal summary table only, without writing a full report:
threatmap scan ./infra/ --summary
Use ASCII-only severity indicators (no emojis) for environments that don't support Unicode:
threatmap scan ./infra/ --ascii --output report.md
Sample Report Output
Running threatmap scan ./examples --output report.md against the bundled examples produces a full Markdown report. Below is a representative excerpt.
STRIDE Threat Table
| 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. |
Mitigation Detail (excerpt)
### 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.
Data Flow Diagram (Mermaid)
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
Advanced Features (v1.1.0+)
Graph-based Attack Path Analysis
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.
Custom YAML Rules
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."
Remediation Hints
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.
Where rules live
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.
How severities are assigned
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) |
How false positives are avoided
- 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.
CI Integration
# .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.md
The --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.
STRIDE Rule Coverage
| 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)
Development
Run tests:
pytest tests/ -v
Run with coverage:
pytest tests/ --cov=threatmap --cov-report=term-missing
Contributing
- 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
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file threatmap-1.1.5.tar.gz.
File metadata
- Download URL: threatmap-1.1.5.tar.gz
- Upload date:
- Size: 44.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
75998e94dc8a59c82a3758bb51719f3d2bd01465692d02094578f1b125a88da5
|
|
| MD5 |
fcf0a97409e4d1a63590897c9438bbbd
|
|
| BLAKE2b-256 |
2d78c99d29260b0a44c9696b6887c3db703aa26ebd4e9d4c1a81b93b59fd0e87
|
File details
Details for the file threatmap-1.1.5-py3-none-any.whl.
File metadata
- Download URL: threatmap-1.1.5-py3-none-any.whl
- Upload date:
- Size: 45.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d1f8b1b8351bf4dc78e16d1f8506f3fad7db2e43d90f79b7bccc3259ab64aa20
|
|
| MD5 |
987cdb94f21dc478d43a71a1137c6615
|
|
| BLAKE2b-256 |
82a4785fe90fcb87f6d61688d3e9b7c8421cf7328de2eb501f2ff757ebd9170a
|