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.
Files changed (38) hide show
  1. threatmap-1.1.1/LICENSE +21 -0
  2. threatmap-1.1.1/PKG-INFO +354 -0
  3. threatmap-1.1.1/README.md +312 -0
  4. threatmap-1.1.1/pyproject.toml +59 -0
  5. threatmap-1.1.1/scripts/export_screenshot.py +104 -0
  6. threatmap-1.1.1/setup.cfg +4 -0
  7. threatmap-1.1.1/setup.py +44 -0
  8. threatmap-1.1.1/tests/test_analyzers.py +344 -0
  9. threatmap-1.1.1/tests/test_improvements.py +74 -0
  10. threatmap-1.1.1/tests/test_parsers.py +180 -0
  11. threatmap-1.1.1/threatmap/__init__.py +1 -0
  12. threatmap-1.1.1/threatmap/__main__.py +7 -0
  13. threatmap-1.1.1/threatmap/analyzers/__init__.py +0 -0
  14. threatmap-1.1.1/threatmap/analyzers/aws.py +514 -0
  15. threatmap-1.1.1/threatmap/analyzers/azure.py +317 -0
  16. threatmap-1.1.1/threatmap/analyzers/engine.py +128 -0
  17. threatmap-1.1.1/threatmap/analyzers/gcp.py +286 -0
  18. threatmap-1.1.1/threatmap/analyzers/kubernetes.py +362 -0
  19. threatmap-1.1.1/threatmap/cli.py +252 -0
  20. threatmap-1.1.1/threatmap/detect.py +66 -0
  21. threatmap-1.1.1/threatmap/models/__init__.py +0 -0
  22. threatmap-1.1.1/threatmap/models/resource.py +18 -0
  23. threatmap-1.1.1/threatmap/models/threat.py +46 -0
  24. threatmap-1.1.1/threatmap/parsers/__init__.py +0 -0
  25. threatmap-1.1.1/threatmap/parsers/cloudformation.py +128 -0
  26. threatmap-1.1.1/threatmap/parsers/kubernetes.py +105 -0
  27. threatmap-1.1.1/threatmap/parsers/terraform.py +131 -0
  28. threatmap-1.1.1/threatmap/reporters/__init__.py +0 -0
  29. threatmap-1.1.1/threatmap/reporters/html_reporter.py +123 -0
  30. threatmap-1.1.1/threatmap/reporters/json_reporter.py +45 -0
  31. threatmap-1.1.1/threatmap/reporters/markdown.py +325 -0
  32. threatmap-1.1.1/threatmap/reporters/sarif_reporter.py +77 -0
  33. threatmap-1.1.1/threatmap.egg-info/PKG-INFO +354 -0
  34. threatmap-1.1.1/threatmap.egg-info/SOURCES.txt +36 -0
  35. threatmap-1.1.1/threatmap.egg-info/dependency_links.txt +1 -0
  36. threatmap-1.1.1/threatmap.egg-info/entry_points.txt +2 -0
  37. threatmap-1.1.1/threatmap.egg-info/requires.txt +11 -0
  38. threatmap-1.1.1/threatmap.egg-info/top_level.txt +4 -0
@@ -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.
@@ -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
+ [![CI](https://github.com/bogdanticu88/threatmap/actions/workflows/ci.yml/badge.svg)](https://github.com/bogdanticu88/threatmap/actions/workflows/ci.yml)
49
+ [![Python](https://img.shields.io/badge/python-3.9%20|%203.10%20|%203.11%20|%203.12-blue)](https://pypi.org/project/threatmap/)
50
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
51
+ [![Offline](https://img.shields.io/badge/offline-no%20network%20calls-lightgrey)](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
+ [![CI](https://github.com/bogdanticu88/threatmap/actions/workflows/ci.yml/badge.svg)](https://github.com/bogdanticu88/threatmap/actions/workflows/ci.yml)
7
+ [![Python](https://img.shields.io/badge/python-3.9%20|%203.10%20|%203.11%20|%203.12-blue)](https://pypi.org/project/threatmap/)
8
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
9
+ [![Offline](https://img.shields.io/badge/offline-no%20network%20calls-lightgrey)](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