threatmap 1.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- threatmap-1.1.1/LICENSE +21 -0
- threatmap-1.1.1/PKG-INFO +354 -0
- threatmap-1.1.1/README.md +312 -0
- threatmap-1.1.1/pyproject.toml +59 -0
- threatmap-1.1.1/scripts/export_screenshot.py +104 -0
- threatmap-1.1.1/setup.cfg +4 -0
- threatmap-1.1.1/setup.py +44 -0
- threatmap-1.1.1/tests/test_analyzers.py +344 -0
- threatmap-1.1.1/tests/test_improvements.py +74 -0
- threatmap-1.1.1/tests/test_parsers.py +180 -0
- threatmap-1.1.1/threatmap/__init__.py +1 -0
- threatmap-1.1.1/threatmap/__main__.py +7 -0
- threatmap-1.1.1/threatmap/analyzers/__init__.py +0 -0
- threatmap-1.1.1/threatmap/analyzers/aws.py +514 -0
- threatmap-1.1.1/threatmap/analyzers/azure.py +317 -0
- threatmap-1.1.1/threatmap/analyzers/engine.py +128 -0
- threatmap-1.1.1/threatmap/analyzers/gcp.py +286 -0
- threatmap-1.1.1/threatmap/analyzers/kubernetes.py +362 -0
- threatmap-1.1.1/threatmap/cli.py +252 -0
- threatmap-1.1.1/threatmap/detect.py +66 -0
- threatmap-1.1.1/threatmap/models/__init__.py +0 -0
- threatmap-1.1.1/threatmap/models/resource.py +18 -0
- threatmap-1.1.1/threatmap/models/threat.py +46 -0
- threatmap-1.1.1/threatmap/parsers/__init__.py +0 -0
- threatmap-1.1.1/threatmap/parsers/cloudformation.py +128 -0
- threatmap-1.1.1/threatmap/parsers/kubernetes.py +105 -0
- threatmap-1.1.1/threatmap/parsers/terraform.py +131 -0
- threatmap-1.1.1/threatmap/reporters/__init__.py +0 -0
- threatmap-1.1.1/threatmap/reporters/html_reporter.py +123 -0
- threatmap-1.1.1/threatmap/reporters/json_reporter.py +45 -0
- threatmap-1.1.1/threatmap/reporters/markdown.py +325 -0
- threatmap-1.1.1/threatmap/reporters/sarif_reporter.py +77 -0
- threatmap-1.1.1/threatmap.egg-info/PKG-INFO +354 -0
- threatmap-1.1.1/threatmap.egg-info/SOURCES.txt +36 -0
- threatmap-1.1.1/threatmap.egg-info/dependency_links.txt +1 -0
- threatmap-1.1.1/threatmap.egg-info/entry_points.txt +2 -0
- threatmap-1.1.1/threatmap.egg-info/requires.txt +11 -0
- threatmap-1.1.1/threatmap.egg-info/top_level.txt +4 -0
threatmap-1.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bogdan Ticu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
threatmap-1.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: threatmap
|
|
3
|
+
Version: 1.1.1
|
|
4
|
+
Summary: Static IaC threat modeler using STRIDE
|
|
5
|
+
Home-page: https://github.com/bogdanticu88/threatmap
|
|
6
|
+
Author: Bogdan Ticu
|
|
7
|
+
Author-email: Bogdan Ticu <bogdanticuoffice@gmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Homepage, https://github.com/bogdanticu88/threatmap
|
|
10
|
+
Project-URL: Repository, https://github.com/bogdanticu88/threatmap
|
|
11
|
+
Project-URL: Issues, https://github.com/bogdanticu88/threatmap/issues
|
|
12
|
+
Keywords: iac,security,threat-modeling,stride,terraform,cloudformation,kubernetes,devsecops
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: System Administrators
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: Security
|
|
24
|
+
Classifier: Topic :: System :: Systems Administration
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: python-hcl2<8.0,>=4.3.0
|
|
29
|
+
Requires-Dist: pyyaml<7.0,>=6.0.1
|
|
30
|
+
Requires-Dist: click<9.0,>=8.1.0
|
|
31
|
+
Requires-Dist: rich<15.0,>=13.0.0
|
|
32
|
+
Requires-Dist: jinja2<4.0,>=3.1.0
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: pytest<9.0,>=7.4.0; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-cov<6.0,>=4.1.0; extra == "dev"
|
|
36
|
+
Requires-Dist: build>=1.0.0; extra == "dev"
|
|
37
|
+
Requires-Dist: twine>=5.0.0; extra == "dev"
|
|
38
|
+
Dynamic: author
|
|
39
|
+
Dynamic: home-page
|
|
40
|
+
Dynamic: license-file
|
|
41
|
+
Dynamic: requires-python
|
|
42
|
+
|
|
43
|
+
<img width="1536" height="1024" alt="image" src="https://github.com/user-attachments/assets/517c5165-6780-4861-a1a3-aa092b6179e4" />
|
|
44
|
+
|
|
45
|
+
# threatmap
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
[](https://github.com/bogdanticu88/threatmap/actions/workflows/ci.yml)
|
|
49
|
+
[](https://pypi.org/project/threatmap/)
|
|
50
|
+
[](LICENSE)
|
|
51
|
+
[](https://github.com/bogdanticu88/threatmap)
|
|
52
|
+
|
|
53
|
+
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.
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Quick Start
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install -e .
|
|
63
|
+
# Run as a command
|
|
64
|
+
threatmap scan ./examples --output report.md --fail-on HIGH
|
|
65
|
+
# Or as a module
|
|
66
|
+
python -m threatmap scan ./examples --ascii
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Supported Formats and Providers
|
|
72
|
+
|
|
73
|
+
| Format | Provider | Extension |
|
|
74
|
+
|--------|----------|-----------|
|
|
75
|
+
| Terraform HCL | AWS, Azure, GCP | `.tf` |
|
|
76
|
+
| CloudFormation | AWS | `.yaml`, `.yml`, `.json` |
|
|
77
|
+
| Kubernetes manifests | Kubernetes | `.yaml`, `.yml` |
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Install
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install -e .
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Or from requirements:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
pip install -r requirements.txt
|
|
91
|
+
pip install -e .
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Usage
|
|
97
|
+
|
|
98
|
+
Scan a directory and print a Markdown report to stdout:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
threatmap scan ./terraform/
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Scan multiple paths and write a JSON report to a file:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
threatmap scan ./terraform/ ./k8s/ ./cloudformation/ --format json --output report.json
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Generate an interactive HTML report or a SARIF report for GitHub Security:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
threatmap scan ./infra/ --format html --output report.html
|
|
114
|
+
threatmap scan ./infra/ --format sarif --output report.sarif
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
CI gate — exit code 1 if any CRITICAL or HIGH threat is found:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
threatmap scan ./infra/ --fail-on HIGH --output threat-report.md
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Print a terminal summary table only, without writing a full report:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
threatmap scan ./infra/ --summary
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Use ASCII-only severity indicators (no emojis) for environments that don't support Unicode:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
threatmap scan ./infra/ --ascii --output report.md
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Sample Report Output
|
|
138
|
+
|
|
139
|
+
Running `threatmap scan ./examples --output report.md` against the bundled examples produces a full Markdown report. Below is a representative excerpt.
|
|
140
|
+
|
|
141
|
+
### STRIDE Threat Table
|
|
142
|
+
|
|
143
|
+
| ID | Severity | STRIDE Category | Resource | Description |
|
|
144
|
+
|----|----------|----------------|----------|-------------|
|
|
145
|
+
| T-001 | 🔴 CRITICAL | Information Disclosure | `AuditBucket` | S3 bucket 'AuditBucket' has no public access block configured — bucket may be publicly accessible. |
|
|
146
|
+
| T-002 | 🔴 CRITICAL | Spoofing | `WebSecurityGroup` | Security group 'WebSecurityGroup' exposes SSH/RDP (port 22/3389) to 0.0.0.0/0. |
|
|
147
|
+
| T-003 | 🔴 CRITICAL | Elevation of Privilege | `app_contributor` | Role assignment 'app_contributor' grants the privileged role 'Contributor'. |
|
|
148
|
+
| T-006 | 🟠 HIGH | Information Disclosure | `AuditBucket` | S3 bucket 'AuditBucket' does not have server-side encryption configured. |
|
|
149
|
+
| T-008 | 🟠 HIGH | Elevation of Privilege | `api` | Container 'api' in Deployment 'api' may run as root (no runAsNonRoot=true or runAsUser=0). |
|
|
150
|
+
| T-011 | 🟠 HIGH | Elevation of Privilege | `web` | EC2 instance 'web' allows IMDSv1 — metadata service accessible without session tokens, enabling SSRF-based credential theft. |
|
|
151
|
+
|
|
152
|
+
### Mitigation Detail (excerpt)
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
### T-002 — Spoofing (CRITICAL)
|
|
156
|
+
|
|
157
|
+
Resource: AWS::EC2::SecurityGroup.WebSecurityGroup
|
|
158
|
+
Property: ingress.ssh_rdp_open
|
|
159
|
+
Finding: Security group 'WebSecurityGroup' exposes SSH/RDP (port 22/3389) to 0.0.0.0/0.
|
|
160
|
+
Mitigation: Remove public SSH/RDP access. Use AWS Systems Manager Session Manager
|
|
161
|
+
or a bastion host with IP restrictions.
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Data Flow Diagram (Mermaid)
|
|
165
|
+
|
|
166
|
+
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.
|
|
167
|
+
|
|
168
|
+
```mermaid
|
|
169
|
+
flowchart LR
|
|
170
|
+
Internet((Internet))
|
|
171
|
+
subgraph Networking
|
|
172
|
+
aws_security_group_web_sg{web_sg}
|
|
173
|
+
NetworkPolicy_default_deny{default-deny}
|
|
174
|
+
azurerm_network_security_group_app_nsg{app_nsg}
|
|
175
|
+
end
|
|
176
|
+
subgraph Compute
|
|
177
|
+
aws_instance_web[web]
|
|
178
|
+
end
|
|
179
|
+
subgraph Kubernetes
|
|
180
|
+
Namespace_myapp[myapp]
|
|
181
|
+
Deployment_api[api]
|
|
182
|
+
Service_api_svc[api-svc]
|
|
183
|
+
Ingress_api_ingress[api-ingress]
|
|
184
|
+
end
|
|
185
|
+
subgraph Data
|
|
186
|
+
aws_s3_bucket_app_data[(app_data)]
|
|
187
|
+
aws_db_instance_app_db[(app_db)]
|
|
188
|
+
azurerm_storage_account_app_storage[(app_storage)]
|
|
189
|
+
end
|
|
190
|
+
subgraph Security
|
|
191
|
+
azurerm_key_vault_app_kv[app_kv]
|
|
192
|
+
end
|
|
193
|
+
subgraph Identity
|
|
194
|
+
azurerm_role_assignment_app_contributor[/app_contributor/]
|
|
195
|
+
end
|
|
196
|
+
AWS__S3__Bucket_AppBucket -->|ref| AWS__S3__Bucket_AuditBucket
|
|
197
|
+
AWS__CloudTrail__Trail_AppTrail -->|ref| AWS__S3__Bucket_AuditBucket
|
|
198
|
+
Internet -->|HTTPS| Ingress_api_ingress
|
|
199
|
+
style aws_security_group_web_sg fill:#ff4444,color:#fff
|
|
200
|
+
style aws_s3_bucket_app_data fill:#ff4444,color:#fff
|
|
201
|
+
style aws_instance_web fill:#ff8800,color:#fff
|
|
202
|
+
style Deployment_api fill:#ff8800,color:#fff
|
|
203
|
+
style azurerm_key_vault_app_kv fill:#ffcc00,color:#000
|
|
204
|
+
style azurerm_network_security_group_app_nsg fill:#ff8800,color:#fff
|
|
205
|
+
style azurerm_role_assignment_app_contributor fill:#ff4444,color:#fff
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Advanced Features (v1.1.0+)
|
|
211
|
+
|
|
212
|
+
### Graph-based Attack Path Analysis
|
|
213
|
+
`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.
|
|
214
|
+
|
|
215
|
+
### Custom YAML Rules
|
|
216
|
+
You can define internal security requirements by creating a `threatmap_rules.yaml` in your project root.
|
|
217
|
+
|
|
218
|
+
```yaml
|
|
219
|
+
rules:
|
|
220
|
+
- resource_type: "aws_s3_bucket"
|
|
221
|
+
property: "force_destroy"
|
|
222
|
+
expected: false
|
|
223
|
+
stride: "Tampering"
|
|
224
|
+
severity: "MEDIUM"
|
|
225
|
+
description: "Production buckets should not have force_destroy enabled."
|
|
226
|
+
mitigation: "Set force_destroy = false."
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Remediation Hints
|
|
230
|
+
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.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
### Where rules live
|
|
235
|
+
|
|
236
|
+
Each cloud provider has its own analyzer module:
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
threatmap/analyzers/
|
|
240
|
+
├── aws.py # 22 rules — S3, IAM, EC2, RDS, EKS, CloudTrail, KMS, Lambda
|
|
241
|
+
├── azure.py # 19 rules — Storage, Key Vault, NSG, RBAC, AKS, ACR, SQL
|
|
242
|
+
├── gcp.py # 15 rules — GCS, Firewall, Compute, Cloud SQL, GKE, IAM, KMS
|
|
243
|
+
└── kubernetes.py # 17 rules — workloads, RBAC, network, secrets
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
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.
|
|
247
|
+
|
|
248
|
+
### How severities are assigned
|
|
249
|
+
|
|
250
|
+
Severity reflects both **exploitability** and **blast radius**:
|
|
251
|
+
|
|
252
|
+
| Severity | Meaning |
|
|
253
|
+
|----------|---------|
|
|
254
|
+
| 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) |
|
|
255
|
+
| HIGH | Significant risk requiring one additional step (e.g. unencrypted RDS with public access, IMDSv1 on an EC2 instance) |
|
|
256
|
+
| MEDIUM | Defence-in-depth controls missing — lower immediate risk but violates security baselines (e.g. no versioning, no logging, no resource limits) |
|
|
257
|
+
| LOW | Best-practice gaps with limited standalone exploitability (e.g. Lambda not in VPC) |
|
|
258
|
+
|
|
259
|
+
### How false positives are avoided
|
|
260
|
+
|
|
261
|
+
- **No heuristics or ML** — every rule fires on a concrete, unambiguous property value (e.g. `publicly_accessible = true`, `Principal: "*"`).
|
|
262
|
+
- **Conservative defaults** — if a property is absent, the rule assumes the insecure default (e.g. no `metadata_options` block on an EC2 instance means IMDSv1 is active, because that is AWS's default).
|
|
263
|
+
- **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.
|
|
264
|
+
- **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.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## CI Integration
|
|
269
|
+
|
|
270
|
+
```yaml
|
|
271
|
+
# .github/workflows/threat-model.yml
|
|
272
|
+
name: Threat Model
|
|
273
|
+
|
|
274
|
+
on: [pull_request]
|
|
275
|
+
|
|
276
|
+
jobs:
|
|
277
|
+
threatmap:
|
|
278
|
+
runs-on: ubuntu-latest
|
|
279
|
+
steps:
|
|
280
|
+
- uses: actions/checkout@v4
|
|
281
|
+
|
|
282
|
+
- name: Set up Python
|
|
283
|
+
uses: actions/setup-python@v5
|
|
284
|
+
with:
|
|
285
|
+
python-version: "3.11"
|
|
286
|
+
|
|
287
|
+
- name: Install threatmap
|
|
288
|
+
run: pip install -r requirements.txt && pip install -e .
|
|
289
|
+
|
|
290
|
+
- name: Run threat model scan
|
|
291
|
+
run: |
|
|
292
|
+
threatmap scan ./infra/ \
|
|
293
|
+
--format markdown \
|
|
294
|
+
--output threat-report.md \
|
|
295
|
+
--fail-on HIGH
|
|
296
|
+
|
|
297
|
+
- name: Upload threat report
|
|
298
|
+
if: always()
|
|
299
|
+
uses: actions/upload-artifact@v4
|
|
300
|
+
with:
|
|
301
|
+
name: threat-report
|
|
302
|
+
path: threat-report.md
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
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.
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## STRIDE Rule Coverage
|
|
310
|
+
|
|
311
|
+
| Provider | Rules |
|
|
312
|
+
|----------|-------|
|
|
313
|
+
| AWS (Terraform + CloudFormation) | 22 |
|
|
314
|
+
| Azure (Terraform) | 19 |
|
|
315
|
+
| GCP (Terraform) | 15 |
|
|
316
|
+
| Kubernetes | 17 |
|
|
317
|
+
| **Total** | **73** |
|
|
318
|
+
|
|
319
|
+
Categories covered per provider:
|
|
320
|
+
|
|
321
|
+
| Provider | S | T | R | I | D | E |
|
|
322
|
+
|----------|---|---|---|---|---|---|
|
|
323
|
+
| AWS | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
324
|
+
| Azure | ✓ | ✓ | ✓ | ✓ | — | ✓ |
|
|
325
|
+
| GCP | ✓ | ✓ | ✓ | ✓ | — | ✓ |
|
|
326
|
+
| Kubernetes | ✓ | ✓ | — | ✓ | ✓ | ✓ |
|
|
327
|
+
|
|
328
|
+
*(S=Spoofing, T=Tampering, R=Repudiation, I=Information Disclosure, D=Denial of Service, E=Elevation of Privilege)*
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Development
|
|
333
|
+
|
|
334
|
+
Run tests:
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
pytest tests/ -v
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Run with coverage:
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
pytest tests/ --cov=threatmap --cov-report=term-missing
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## Contributing
|
|
349
|
+
|
|
350
|
+
1. Fork the repository
|
|
351
|
+
2. Add rules in `threatmap/analyzers/<provider>.py` following the existing pattern
|
|
352
|
+
3. Add a fixture in `tests/fixtures/` that triggers the new rule
|
|
353
|
+
4. Add assertions in `tests/test_analyzers.py`
|
|
354
|
+
5. Open a pull request
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
<img width="1536" height="1024" alt="image" src="https://github.com/user-attachments/assets/517c5165-6780-4861-a1a3-aa092b6179e4" />
|
|
2
|
+
|
|
3
|
+
# threatmap
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
[](https://github.com/bogdanticu88/threatmap/actions/workflows/ci.yml)
|
|
7
|
+
[](https://pypi.org/project/threatmap/)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](https://github.com/bogdanticu88/threatmap)
|
|
10
|
+
|
|
11
|
+
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.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install -e .
|
|
21
|
+
# Run as a command
|
|
22
|
+
threatmap scan ./examples --output report.md --fail-on HIGH
|
|
23
|
+
# Or as a module
|
|
24
|
+
python -m threatmap scan ./examples --ascii
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Supported Formats and Providers
|
|
30
|
+
|
|
31
|
+
| Format | Provider | Extension |
|
|
32
|
+
|--------|----------|-----------|
|
|
33
|
+
| Terraform HCL | AWS, Azure, GCP | `.tf` |
|
|
34
|
+
| CloudFormation | AWS | `.yaml`, `.yml`, `.json` |
|
|
35
|
+
| Kubernetes manifests | Kubernetes | `.yaml`, `.yml` |
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install -e .
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Or from requirements:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install -r requirements.txt
|
|
49
|
+
pip install -e .
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
Scan a directory and print a Markdown report to stdout:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
threatmap scan ./terraform/
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Scan multiple paths and write a JSON report to a file:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
threatmap scan ./terraform/ ./k8s/ ./cloudformation/ --format json --output report.json
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Generate an interactive HTML report or a SARIF report for GitHub Security:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
threatmap scan ./infra/ --format html --output report.html
|
|
72
|
+
threatmap scan ./infra/ --format sarif --output report.sarif
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
CI gate — exit code 1 if any CRITICAL or HIGH threat is found:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
threatmap scan ./infra/ --fail-on HIGH --output threat-report.md
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Print a terminal summary table only, without writing a full report:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
threatmap scan ./infra/ --summary
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Use ASCII-only severity indicators (no emojis) for environments that don't support Unicode:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
threatmap scan ./infra/ --ascii --output report.md
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Sample Report Output
|
|
96
|
+
|
|
97
|
+
Running `threatmap scan ./examples --output report.md` against the bundled examples produces a full Markdown report. Below is a representative excerpt.
|
|
98
|
+
|
|
99
|
+
### STRIDE Threat Table
|
|
100
|
+
|
|
101
|
+
| ID | Severity | STRIDE Category | Resource | Description |
|
|
102
|
+
|----|----------|----------------|----------|-------------|
|
|
103
|
+
| T-001 | 🔴 CRITICAL | Information Disclosure | `AuditBucket` | S3 bucket 'AuditBucket' has no public access block configured — bucket may be publicly accessible. |
|
|
104
|
+
| T-002 | 🔴 CRITICAL | Spoofing | `WebSecurityGroup` | Security group 'WebSecurityGroup' exposes SSH/RDP (port 22/3389) to 0.0.0.0/0. |
|
|
105
|
+
| T-003 | 🔴 CRITICAL | Elevation of Privilege | `app_contributor` | Role assignment 'app_contributor' grants the privileged role 'Contributor'. |
|
|
106
|
+
| T-006 | 🟠 HIGH | Information Disclosure | `AuditBucket` | S3 bucket 'AuditBucket' does not have server-side encryption configured. |
|
|
107
|
+
| T-008 | 🟠 HIGH | Elevation of Privilege | `api` | Container 'api' in Deployment 'api' may run as root (no runAsNonRoot=true or runAsUser=0). |
|
|
108
|
+
| T-011 | 🟠 HIGH | Elevation of Privilege | `web` | EC2 instance 'web' allows IMDSv1 — metadata service accessible without session tokens, enabling SSRF-based credential theft. |
|
|
109
|
+
|
|
110
|
+
### Mitigation Detail (excerpt)
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
### T-002 — Spoofing (CRITICAL)
|
|
114
|
+
|
|
115
|
+
Resource: AWS::EC2::SecurityGroup.WebSecurityGroup
|
|
116
|
+
Property: ingress.ssh_rdp_open
|
|
117
|
+
Finding: Security group 'WebSecurityGroup' exposes SSH/RDP (port 22/3389) to 0.0.0.0/0.
|
|
118
|
+
Mitigation: Remove public SSH/RDP access. Use AWS Systems Manager Session Manager
|
|
119
|
+
or a bastion host with IP restrictions.
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Data Flow Diagram (Mermaid)
|
|
123
|
+
|
|
124
|
+
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.
|
|
125
|
+
|
|
126
|
+
```mermaid
|
|
127
|
+
flowchart LR
|
|
128
|
+
Internet((Internet))
|
|
129
|
+
subgraph Networking
|
|
130
|
+
aws_security_group_web_sg{web_sg}
|
|
131
|
+
NetworkPolicy_default_deny{default-deny}
|
|
132
|
+
azurerm_network_security_group_app_nsg{app_nsg}
|
|
133
|
+
end
|
|
134
|
+
subgraph Compute
|
|
135
|
+
aws_instance_web[web]
|
|
136
|
+
end
|
|
137
|
+
subgraph Kubernetes
|
|
138
|
+
Namespace_myapp[myapp]
|
|
139
|
+
Deployment_api[api]
|
|
140
|
+
Service_api_svc[api-svc]
|
|
141
|
+
Ingress_api_ingress[api-ingress]
|
|
142
|
+
end
|
|
143
|
+
subgraph Data
|
|
144
|
+
aws_s3_bucket_app_data[(app_data)]
|
|
145
|
+
aws_db_instance_app_db[(app_db)]
|
|
146
|
+
azurerm_storage_account_app_storage[(app_storage)]
|
|
147
|
+
end
|
|
148
|
+
subgraph Security
|
|
149
|
+
azurerm_key_vault_app_kv[app_kv]
|
|
150
|
+
end
|
|
151
|
+
subgraph Identity
|
|
152
|
+
azurerm_role_assignment_app_contributor[/app_contributor/]
|
|
153
|
+
end
|
|
154
|
+
AWS__S3__Bucket_AppBucket -->|ref| AWS__S3__Bucket_AuditBucket
|
|
155
|
+
AWS__CloudTrail__Trail_AppTrail -->|ref| AWS__S3__Bucket_AuditBucket
|
|
156
|
+
Internet -->|HTTPS| Ingress_api_ingress
|
|
157
|
+
style aws_security_group_web_sg fill:#ff4444,color:#fff
|
|
158
|
+
style aws_s3_bucket_app_data fill:#ff4444,color:#fff
|
|
159
|
+
style aws_instance_web fill:#ff8800,color:#fff
|
|
160
|
+
style Deployment_api fill:#ff8800,color:#fff
|
|
161
|
+
style azurerm_key_vault_app_kv fill:#ffcc00,color:#000
|
|
162
|
+
style azurerm_network_security_group_app_nsg fill:#ff8800,color:#fff
|
|
163
|
+
style azurerm_role_assignment_app_contributor fill:#ff4444,color:#fff
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Advanced Features (v1.1.0+)
|
|
169
|
+
|
|
170
|
+
### Graph-based Attack Path Analysis
|
|
171
|
+
`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.
|
|
172
|
+
|
|
173
|
+
### Custom YAML Rules
|
|
174
|
+
You can define internal security requirements by creating a `threatmap_rules.yaml` in your project root.
|
|
175
|
+
|
|
176
|
+
```yaml
|
|
177
|
+
rules:
|
|
178
|
+
- resource_type: "aws_s3_bucket"
|
|
179
|
+
property: "force_destroy"
|
|
180
|
+
expected: false
|
|
181
|
+
stride: "Tampering"
|
|
182
|
+
severity: "MEDIUM"
|
|
183
|
+
description: "Production buckets should not have force_destroy enabled."
|
|
184
|
+
mitigation: "Set force_destroy = false."
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Remediation Hints
|
|
188
|
+
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.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
### Where rules live
|
|
193
|
+
|
|
194
|
+
Each cloud provider has its own analyzer module:
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
threatmap/analyzers/
|
|
198
|
+
├── aws.py # 22 rules — S3, IAM, EC2, RDS, EKS, CloudTrail, KMS, Lambda
|
|
199
|
+
├── azure.py # 19 rules — Storage, Key Vault, NSG, RBAC, AKS, ACR, SQL
|
|
200
|
+
├── gcp.py # 15 rules — GCS, Firewall, Compute, Cloud SQL, GKE, IAM, KMS
|
|
201
|
+
└── kubernetes.py # 17 rules — workloads, RBAC, network, secrets
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
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.
|
|
205
|
+
|
|
206
|
+
### How severities are assigned
|
|
207
|
+
|
|
208
|
+
Severity reflects both **exploitability** and **blast radius**:
|
|
209
|
+
|
|
210
|
+
| Severity | Meaning |
|
|
211
|
+
|----------|---------|
|
|
212
|
+
| 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) |
|
|
213
|
+
| HIGH | Significant risk requiring one additional step (e.g. unencrypted RDS with public access, IMDSv1 on an EC2 instance) |
|
|
214
|
+
| MEDIUM | Defence-in-depth controls missing — lower immediate risk but violates security baselines (e.g. no versioning, no logging, no resource limits) |
|
|
215
|
+
| LOW | Best-practice gaps with limited standalone exploitability (e.g. Lambda not in VPC) |
|
|
216
|
+
|
|
217
|
+
### How false positives are avoided
|
|
218
|
+
|
|
219
|
+
- **No heuristics or ML** — every rule fires on a concrete, unambiguous property value (e.g. `publicly_accessible = true`, `Principal: "*"`).
|
|
220
|
+
- **Conservative defaults** — if a property is absent, the rule assumes the insecure default (e.g. no `metadata_options` block on an EC2 instance means IMDSv1 is active, because that is AWS's default).
|
|
221
|
+
- **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.
|
|
222
|
+
- **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.
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## CI Integration
|
|
227
|
+
|
|
228
|
+
```yaml
|
|
229
|
+
# .github/workflows/threat-model.yml
|
|
230
|
+
name: Threat Model
|
|
231
|
+
|
|
232
|
+
on: [pull_request]
|
|
233
|
+
|
|
234
|
+
jobs:
|
|
235
|
+
threatmap:
|
|
236
|
+
runs-on: ubuntu-latest
|
|
237
|
+
steps:
|
|
238
|
+
- uses: actions/checkout@v4
|
|
239
|
+
|
|
240
|
+
- name: Set up Python
|
|
241
|
+
uses: actions/setup-python@v5
|
|
242
|
+
with:
|
|
243
|
+
python-version: "3.11"
|
|
244
|
+
|
|
245
|
+
- name: Install threatmap
|
|
246
|
+
run: pip install -r requirements.txt && pip install -e .
|
|
247
|
+
|
|
248
|
+
- name: Run threat model scan
|
|
249
|
+
run: |
|
|
250
|
+
threatmap scan ./infra/ \
|
|
251
|
+
--format markdown \
|
|
252
|
+
--output threat-report.md \
|
|
253
|
+
--fail-on HIGH
|
|
254
|
+
|
|
255
|
+
- name: Upload threat report
|
|
256
|
+
if: always()
|
|
257
|
+
uses: actions/upload-artifact@v4
|
|
258
|
+
with:
|
|
259
|
+
name: threat-report
|
|
260
|
+
path: threat-report.md
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
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.
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## STRIDE Rule Coverage
|
|
268
|
+
|
|
269
|
+
| Provider | Rules |
|
|
270
|
+
|----------|-------|
|
|
271
|
+
| AWS (Terraform + CloudFormation) | 22 |
|
|
272
|
+
| Azure (Terraform) | 19 |
|
|
273
|
+
| GCP (Terraform) | 15 |
|
|
274
|
+
| Kubernetes | 17 |
|
|
275
|
+
| **Total** | **73** |
|
|
276
|
+
|
|
277
|
+
Categories covered per provider:
|
|
278
|
+
|
|
279
|
+
| Provider | S | T | R | I | D | E |
|
|
280
|
+
|----------|---|---|---|---|---|---|
|
|
281
|
+
| AWS | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
282
|
+
| Azure | ✓ | ✓ | ✓ | ✓ | — | ✓ |
|
|
283
|
+
| GCP | ✓ | ✓ | ✓ | ✓ | — | ✓ |
|
|
284
|
+
| Kubernetes | ✓ | ✓ | — | ✓ | ✓ | ✓ |
|
|
285
|
+
|
|
286
|
+
*(S=Spoofing, T=Tampering, R=Repudiation, I=Information Disclosure, D=Denial of Service, E=Elevation of Privilege)*
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Development
|
|
291
|
+
|
|
292
|
+
Run tests:
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
pytest tests/ -v
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Run with coverage:
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
pytest tests/ --cov=threatmap --cov-report=term-missing
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## Contributing
|
|
307
|
+
|
|
308
|
+
1. Fork the repository
|
|
309
|
+
2. Add rules in `threatmap/analyzers/<provider>.py` following the existing pattern
|
|
310
|
+
3. Add a fixture in `tests/fixtures/` that triggers the new rule
|
|
311
|
+
4. Add assertions in `tests/test_analyzers.py`
|
|
312
|
+
5. Open a pull request
|