attackmap 0.6.4__py3-none-any.whl

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 (91) hide show
  1. attackmap/__init__.py +2 -0
  2. attackmap/analyzer.py +449 -0
  3. attackmap/analyzer_contracts.py +194 -0
  4. attackmap/analyzers.py +1003 -0
  5. attackmap/anomalies.py +612 -0
  6. attackmap/asset_model.py +245 -0
  7. attackmap/attack_taxonomy.py +133 -0
  8. attackmap/auth_protocol.py +702 -0
  9. attackmap/authz.py +1695 -0
  10. attackmap/bench.py +467 -0
  11. attackmap/cli.py +1474 -0
  12. attackmap/config_scanner.py +389 -0
  13. attackmap/context_pack.py +82 -0
  14. attackmap/contracts.py +201 -0
  15. attackmap/control_model.py +280 -0
  16. attackmap/crossrepo.py +356 -0
  17. attackmap/crypto.py +240 -0
  18. attackmap/cve.py +717 -0
  19. attackmap/defensive_review.py +647 -0
  20. attackmap/detection_opportunities.py +1221 -0
  21. attackmap/detections/__init__.py +491 -0
  22. attackmap/detections/bola.yml +57 -0
  23. attackmap/detections/cloud.yml +609 -0
  24. attackmap/detections/injection.yml +167 -0
  25. attackmap/detections/secrets.yml +48 -0
  26. attackmap/detections/sigma.py +511 -0
  27. attackmap/detections/ssrf.yml +98 -0
  28. attackmap/detections/weakness.yml +62 -0
  29. attackmap/detections/webhook.yml +45 -0
  30. attackmap/detections/workflow.yml +121 -0
  31. attackmap/diagrams.py +237 -0
  32. attackmap/diff.py +301 -0
  33. attackmap/exploitability.py +493 -0
  34. attackmap/fingerprint.py +91 -0
  35. attackmap/fleet.py +370 -0
  36. attackmap/gitref.py +129 -0
  37. attackmap/graph.py +26 -0
  38. attackmap/handler_auth.py +639 -0
  39. attackmap/handler_span.py +62 -0
  40. attackmap/hunt_harness.py +505 -0
  41. attackmap/insights.py +701 -0
  42. attackmap/llm_review.py +846 -0
  43. attackmap/lockfiles.py +521 -0
  44. attackmap/mass_assignment.py +543 -0
  45. attackmap/md.py +175 -0
  46. attackmap/merge.py +333 -0
  47. attackmap/models.py +1284 -0
  48. attackmap/plugins_lock.py +114 -0
  49. attackmap/popular_packages.py +193 -0
  50. attackmap/progress.py +258 -0
  51. attackmap/recon_models.py +31 -0
  52. attackmap/recon_to_analysis.py +149 -0
  53. attackmap/redact.py +220 -0
  54. attackmap/report.py +424 -0
  55. attackmap/review_eval.py +335 -0
  56. attackmap/review_json.py +308 -0
  57. attackmap/review_prompts.py +796 -0
  58. attackmap/route_auth_fusion.py +474 -0
  59. attackmap/safe_fs.py +285 -0
  60. attackmap/sarif.py +373 -0
  61. attackmap/sbom.py +418 -0
  62. attackmap/scanner.py +1379 -0
  63. attackmap/sdk/__init__.py +278 -0
  64. attackmap/sdk/contracts.py +17 -0
  65. attackmap/sdk/fs.py +156 -0
  66. attackmap/sdk/models.py +59 -0
  67. attackmap/secrets_history.py +161 -0
  68. attackmap/security_overlay.py +71 -0
  69. attackmap/srcpaths.py +301 -0
  70. attackmap/suggest.py +394 -0
  71. attackmap/supply_chain.py +688 -0
  72. attackmap/suppress.py +740 -0
  73. attackmap/taint.py +1540 -0
  74. attackmap/taint_flow.py +1419 -0
  75. attackmap/taint_sinks.py +431 -0
  76. attackmap/taint_sinks.yaml +308 -0
  77. attackmap/taint_sources.py +396 -0
  78. attackmap/taxonomy.py +465 -0
  79. attackmap/threat_model.py +3441 -0
  80. attackmap/topology.py +316 -0
  81. attackmap/triage.py +151 -0
  82. attackmap/weaknesses.py +365 -0
  83. attackmap/webhardening.py +479 -0
  84. attackmap/webhook_verification.py +1067 -0
  85. attackmap/workflow_scanner.py +964 -0
  86. attackmap-0.6.4.dist-info/METADATA +226 -0
  87. attackmap-0.6.4.dist-info/RECORD +91 -0
  88. attackmap-0.6.4.dist-info/WHEEL +5 -0
  89. attackmap-0.6.4.dist-info/entry_points.txt +2 -0
  90. attackmap-0.6.4.dist-info/licenses/LICENSE +21 -0
  91. attackmap-0.6.4.dist-info/top_level.txt +1 -0
attackmap/taxonomy.py ADDED
@@ -0,0 +1,465 @@
1
+ """Per-detector taxonomy registry (#250): CWE, OWASP, ASVS and ATT&CK ids
2
+ keyed by stable rule id (#223).
3
+
4
+ Taxonomy is looked up by ``Finding.rule_id``. It is never inferred from a
5
+ finding's title or evidence text. Every id in ``threat_model.rule_catalog()``
6
+ must be registered here with at least one CWE and one OWASP category;
7
+ ``tests/test_taxonomy.py`` fails when a rule is missing, so a new detector
8
+ cannot ship without a mapping.
9
+
10
+ Registering a new rule
11
+ ----------------------
12
+ Add one line to ``RULE_TAXONOMY``::
13
+
14
+ "my-new-rule": RuleTaxonomy(cwe=(918,), owasp=("A10:2021", "API7:2023"),
15
+ asvs=("V12.6.1",), attack=("T1190",)),
16
+
17
+ - ``cwe``: CWE ids as ints, most specific first. The first one is the
18
+ primary CWE: it becomes the SARIF ``helpUri``. Add any new id and its
19
+ official name to ``CWE_NAMES``.
20
+ - ``owasp``: OWASP Top 10 2021 (``A01:2021``…``A10:2021``), API Security
21
+ Top 10 2023 (``API1:2023``…``API10:2023``) or LLM Top 10 2025 ids, as
22
+ listed in ``OWASP_CATEGORIES``.
23
+ - ``asvs``: optional ASVS 4.0.3 requirement ids (``V5.3.4``).
24
+ - ``attack``: optional ATT&CK Enterprise technique or sub-technique ids.
25
+ Add new ones to ``ATTACK_TECHNIQUES`` with their name and tactic. When
26
+ set, these replace whatever ``attack_techniques`` the detector passed.
27
+
28
+ ``speculative-<rule>`` (recall mode) ids inherit their base rule's entry.
29
+ Rules that flag no weakness at all go in ``INFORMATIONAL_RULES`` instead.
30
+
31
+ Verify ids against the source, not memory: cwe.mitre.org (CWE view 1000),
32
+ owasp.org/Top10, owasp.org/API-Security, the ASVS 4.0.3 JSON release and
33
+ the MITRE ATT&CK STIX bundle. The names below were checked against CWE
34
+ 4.x view 1000, OWASP Top 10:2021, OWASP API Top 10:2023, OWASP LLM Top
35
+ 10:2025, ASVS 4.0.3 and Enterprise ATT&CK 19.2.
36
+
37
+ This module has no package-internal imports, so ``models`` can use it.
38
+ """
39
+
40
+ from __future__ import annotations
41
+
42
+ import re
43
+ from dataclasses import dataclass
44
+
45
+
46
+ @dataclass(frozen=True)
47
+ class RuleTaxonomy:
48
+ cwe: tuple[int, ...]
49
+ owasp: tuple[str, ...]
50
+ asvs: tuple[str, ...] = ()
51
+ attack: tuple[str, ...] = ()
52
+
53
+ @property
54
+ def cwe_ids(self) -> list[str]:
55
+ return [f"CWE-{n}" for n in self.cwe]
56
+
57
+
58
+ # CWE names (CWE view 1000, "Research Concepts").
59
+ CWE_NAMES: dict[int, str] = {
60
+ 15: "External Control of System or Configuration Setting",
61
+ 20: "Improper Input Validation",
62
+ 22: "Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')",
63
+ 74: "Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')",
64
+ 78: "Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')",
65
+ 79: "Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')",
66
+ 89: "Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')",
67
+ 90: "Improper Neutralization of Special Elements used in an LDAP Query ('LDAP Injection')",
68
+ 94: "Improper Control of Generation of Code ('Code Injection')",
69
+ 95: "Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection')",
70
+ 98: "Improper Control of Filename for Include/Require Statement in PHP Program ('PHP Remote File Inclusion')",
71
+ 113: "Improper Neutralization of CRLF Sequences in HTTP Headers ('HTTP Request/Response Splitting')",
72
+ 114: "Process Control",
73
+ 120: "Buffer Copy without Checking Size of Input ('Classic Buffer Overflow')",
74
+ 121: "Stack-based Buffer Overflow",
75
+ 134: "Use of Externally-Controlled Format String",
76
+ 190: "Integer Overflow or Wraparound",
77
+ 200: "Exposure of Sensitive Information to an Unauthorized Actor",
78
+ 208: "Observable Timing Discrepancy",
79
+ 214: "Invocation of Process Using Visible Sensitive Information",
80
+ 215: "Insertion of Sensitive Information Into Debugging Code",
81
+ 242: "Use of Inherently Dangerous Function",
82
+ 250: "Execution with Unnecessary Privileges",
83
+ 269: "Improper Privilege Management",
84
+ 284: "Improper Access Control",
85
+ 285: "Improper Authorization",
86
+ 287: "Improper Authentication",
87
+ 294: "Authentication Bypass by Capture-replay",
88
+ 295: "Improper Certificate Validation",
89
+ 306: "Missing Authentication for Critical Function",
90
+ 307: "Improper Restriction of Excessive Authentication Attempts",
91
+ 311: "Missing Encryption of Sensitive Data",
92
+ 312: "Cleartext Storage of Sensitive Information",
93
+ 319: "Cleartext Transmission of Sensitive Information",
94
+ 326: "Inadequate Encryption Strength",
95
+ 327: "Use of a Broken or Risky Cryptographic Algorithm",
96
+ 328: "Use of Weak Hash",
97
+ 338: "Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG)",
98
+ 345: "Insufficient Verification of Data Authenticity",
99
+ 346: "Origin Validation Error",
100
+ 347: "Improper Verification of Cryptographic Signature",
101
+ 349: "Acceptance of Extraneous Untrusted Data With Trusted Data",
102
+ 352: "Cross-Site Request Forgery (CSRF)",
103
+ 353: "Missing Support for Integrity Check",
104
+ 384: "Session Fixation",
105
+ 427: "Uncontrolled Search Path Element",
106
+ 434: "Unrestricted Upload of File with Dangerous Type",
107
+ 470: "Use of Externally-Controlled Input to Select Classes or Code ('Unsafe Reflection')",
108
+ 489: "Active Debug Code",
109
+ 494: "Download of Code Without Integrity Check",
110
+ 501: "Trust Boundary Violation",
111
+ 502: "Deserialization of Untrusted Data",
112
+ 522: "Insufficiently Protected Credentials",
113
+ 526: "Cleartext Storage of Sensitive Information in an Environment Variable",
114
+ 532: "Insertion of Sensitive Information into Log File",
115
+ 540: "Inclusion of Sensitive Information in Source Code",
116
+ 601: "URL Redirection to Untrusted Site ('Open Redirect')",
117
+ 611: "Improper Restriction of XML External Entity Reference",
118
+ 613: "Insufficient Session Expiration",
119
+ 614: "Sensitive Cookie in HTTPS Session Without 'Secure' Attribute",
120
+ 639: "Authorization Bypass Through User-Controlled Key",
121
+ 643: "Improper Neutralization of Data within XPath Expressions ('XPath Injection')",
122
+ 668: "Exposure of Resource to Wrong Sphere",
123
+ 680: "Integer Overflow to Buffer Overflow",
124
+ 693: "Protection Mechanism Failure",
125
+ 732: "Incorrect Permission Assignment for Critical Resource",
126
+ 749: "Exposed Dangerous Method or Function",
127
+ 760: "Use of a One-Way Hash with a Predictable Salt",
128
+ 770: "Allocation of Resources Without Limits or Throttling",
129
+ 787: "Out-of-bounds Write",
130
+ 798: "Use of Hard-coded Credentials",
131
+ 805: "Buffer Access with Incorrect Length Value",
132
+ 829: "Inclusion of Functionality from Untrusted Control Sphere",
133
+ 862: "Missing Authorization",
134
+ 915: "Improperly Controlled Modification of Dynamically-Determined Object Attributes",
135
+ 916: "Use of Password Hash With Insufficient Computational Effort",
136
+ 917: "Improper Neutralization of Special Elements used in an Expression Language Statement ('Expression Language Injection')",
137
+ 918: "Server-Side Request Forgery (SSRF)",
138
+ 922: "Insecure Storage of Sensitive Information",
139
+ 939: "Improper Authorization in Handler for Custom URL Scheme",
140
+ 942: "Permissive Cross-domain Security Policy with Untrusted Domains",
141
+ 943: "Improper Neutralization of Special Elements in Data Query Logic",
142
+ 1004: "Sensitive Cookie Without 'HttpOnly' Flag",
143
+ 1188: "Initialization of a Resource with an Insecure Default",
144
+ 1204: "Generation of Weak Initialization Vector (IV)",
145
+ 1321: "Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')",
146
+ 1333: "Inefficient Regular Expression Complexity",
147
+ 1336: "Improper Neutralization of Special Elements Used in a Template Engine",
148
+ 1357: "Reliance on Insufficiently Trustworthy Component",
149
+ 1395: "Dependency on Vulnerable Third-Party Component",
150
+ 1427: "Improper Neutralization of Input Used for LLM Prompting",
151
+ }
152
+
153
+ OWASP_CATEGORIES: dict[str, str] = {
154
+ "A01:2021": "Broken Access Control",
155
+ "A02:2021": "Cryptographic Failures",
156
+ "A03:2021": "Injection",
157
+ "A04:2021": "Insecure Design",
158
+ "A05:2021": "Security Misconfiguration",
159
+ "A06:2021": "Vulnerable and Outdated Components",
160
+ "A07:2021": "Identification and Authentication Failures",
161
+ "A08:2021": "Software and Data Integrity Failures",
162
+ "A09:2021": "Security Logging and Monitoring Failures",
163
+ "A10:2021": "Server-Side Request Forgery (SSRF)",
164
+ "API1:2023": "Broken Object Level Authorization",
165
+ "API2:2023": "Broken Authentication",
166
+ "API3:2023": "Broken Object Property Level Authorization",
167
+ "API4:2023": "Unrestricted Resource Consumption",
168
+ "API5:2023": "Broken Function Level Authorization",
169
+ "API6:2023": "Unrestricted Access to Sensitive Business Flows",
170
+ "API7:2023": "Server Side Request Forgery",
171
+ "API8:2023": "Security Misconfiguration",
172
+ "API9:2023": "Improper Inventory Management",
173
+ "API10:2023": "Unsafe Consumption of APIs",
174
+ "LLM01:2025": "Prompt Injection",
175
+ }
176
+
177
+ # ATT&CK Enterprise techniques: id -> (name, tactic(s)). Sub-technique
178
+ # names are "Parent: Sub". Tactic names follow Enterprise ATT&CK 19.
179
+ ATTACK_TECHNIQUES: dict[str, tuple[str, str]] = {
180
+ "T1027": ("Obfuscated Files or Information", "Stealth"),
181
+ "T1041": ("Exfiltration Over C2 Channel", "Exfiltration"),
182
+ "T1055": ("Process Injection", "Stealth / Privilege Escalation"),
183
+ "T1059": ("Command and Scripting Interpreter", "Execution"),
184
+ "T1059.004": ("Command and Scripting Interpreter: Unix Shell", "Execution"),
185
+ "T1059.007": ("Command and Scripting Interpreter: JavaScript", "Execution"),
186
+ "T1068": ("Exploitation for Privilege Escalation", "Privilege Escalation"),
187
+ "T1071": ("Application Layer Protocol", "Command and Control"),
188
+ "T1078": ("Valid Accounts", "Stealth / Persistence / Privilege Escalation / Initial Access"),
189
+ "T1078.004": ("Valid Accounts: Cloud Accounts", "Stealth / Persistence / Privilege Escalation / Initial Access"),
190
+ "T1098": ("Account Manipulation", "Persistence / Privilege Escalation"),
191
+ "T1105": ("Ingress Tool Transfer", "Command and Control"),
192
+ "T1110": ("Brute Force", "Credential Access"),
193
+ "T1110.002": ("Brute Force: Password Cracking", "Credential Access"),
194
+ "T1115": ("Clipboard Data", "Collection"),
195
+ "T1129": ("Shared Modules", "Execution"),
196
+ "T1133": ("External Remote Services", "Persistence / Initial Access"),
197
+ "T1190": ("Exploit Public-Facing Application", "Initial Access"),
198
+ "T1195.001": ("Supply Chain Compromise: Compromise Software Dependencies and Development Tools", "Initial Access"),
199
+ "T1195.002": ("Supply Chain Compromise: Compromise Software Supply Chain", "Initial Access"),
200
+ "T1199": ("Trusted Relationship", "Initial Access"),
201
+ "T1204.001": ("User Execution: Malicious Link", "Execution"),
202
+ "T1212": ("Exploitation for Credential Access", "Credential Access"),
203
+ "T1485": ("Data Destruction", "Impact"),
204
+ "T1499.003": ("Endpoint Denial of Service: Application Exhaustion Flood", "Impact"),
205
+ "T1499.004": ("Endpoint Denial of Service: Application or System Exploitation", "Impact"),
206
+ "T1505.003": ("Server Software Component: Web Shell", "Persistence"),
207
+ "T1548.001": ("Abuse Elevation Control Mechanism: Setuid and Setgid", "Privilege Escalation / Stealth"),
208
+ "T1528": ("Steal Application Access Token", "Credential Access"),
209
+ "T1530": ("Data from Cloud Storage", "Collection"),
210
+ "T1550.001": ("Use Alternate Authentication Material: Application Access Token", "Lateral Movement"),
211
+ "T1539": ("Steal Web Session Cookie", "Credential Access"),
212
+ "T1552": ("Unsecured Credentials", "Credential Access"),
213
+ "T1552.001": ("Unsecured Credentials: Credentials In Files", "Credential Access"),
214
+ "T1552.005": ("Unsecured Credentials: Cloud Instance Metadata API", "Credential Access"),
215
+ "T1552.007": ("Unsecured Credentials: Container API", "Credential Access"),
216
+ "T1556": ("Modify Authentication Process", "Defense Impairment / Persistence / Credential Access"),
217
+ "T1557": ("Adversary-in-the-Middle", "Credential Access / Collection"),
218
+ "T1565": ("Data Manipulation", "Impact"),
219
+ "T1566.002": ("Phishing: Spearphishing Link", "Initial Access"),
220
+ "T1574": ("Hijack Execution Flow", "Stealth / Execution"),
221
+ "T1584.004": ("Compromise Infrastructure: Server", "Resource Development"),
222
+ "T1600": ("Weaken Encryption", "Defense Impairment"),
223
+ "T1606": ("Forge Web Credentials", "Credential Access"),
224
+ "T1609": ("Container Administration Command", "Execution"),
225
+ "T1611": ("Escape to Host", "Privilege Escalation"),
226
+ "T1685": ("Disable or Modify Tools", "Defense Impairment"),
227
+ }
228
+
229
+
230
+ def attack_url(technique_id: str) -> str:
231
+ return f"https://attack.mitre.org/techniques/{technique_id.replace('.', '/')}/"
232
+
233
+
234
+ def cwe_tag(cwe: int) -> str:
235
+ """CodeQL-style CWE tag (``external/cwe/cwe-078``) that GitHub Code
236
+ Scanning links to the CWE entry."""
237
+ return f"external/cwe/cwe-{cwe:03d}"
238
+
239
+
240
+ def cwe_url(cwe: int) -> str:
241
+ return f"https://cwe.mitre.org/data/definitions/{cwe}.html"
242
+
243
+
244
+ # Rules that report no weakness (nothing to map).
245
+ INFORMATIONAL_RULES: frozenset[str] = frozenset({"limited-surface"})
246
+
247
+ RULE_TAXONOMY: dict[str, RuleTaxonomy] = {
248
+ # Route / surface heuristics
249
+ "public-webhook": RuleTaxonomy(cwe=(345,), owasp=("A08:2021",), asvs=("V13.2.6",), attack=("T1190", "T1199")),
250
+ "webhook-weak-verification": RuleTaxonomy(cwe=(208, 294, 345), owasp=("A08:2021",), asvs=("V13.2.6",), attack=("T1190", "T1199")),
251
+ "exposed-admin-route": RuleTaxonomy(cwe=(285,), owasp=("A01:2021", "API5:2023"), asvs=("V4.1.3",), attack=("T1078", "T1068", "T1098")),
252
+ "upload-route": RuleTaxonomy(cwe=(434,), owasp=("A04:2021",), asvs=("V12.2.1", "V12.5.2"), attack=("T1190", "T1505.003")),
253
+ "weak-auth-route": RuleTaxonomy(cwe=(287, 307), owasp=("A07:2021", "API2:2023"), asvs=("V2.2.1",), attack=("T1110", "T1078")),
254
+ "unauth-outbound-integration": RuleTaxonomy(cwe=(306,), owasp=("A07:2021", "API10:2023"), asvs=("V1.2.2",), attack=("T1190", "T1199")),
255
+ "hardcoded-secret": RuleTaxonomy(cwe=(798,), owasp=("A07:2021",), asvs=("V2.10.4", "V6.4.1"), attack=("T1552.001",)),
256
+ "secret-in-git-history": RuleTaxonomy(cwe=(798, 540), owasp=("A07:2021", "A02:2021"), asvs=("V2.10.4", "V6.4.1"), attack=("T1552.001",)),
257
+ "secret-env-reference": RuleTaxonomy(cwe=(526,), owasp=("A05:2021",), asvs=("V6.4.1",), attack=("T1552",)),
258
+ "public-data-route": RuleTaxonomy(cwe=(284, 200), owasp=("A01:2021",), asvs=("V1.4.4", "V8.3.4"), attack=("T1190",)),
259
+ "atproto-trust-chain": RuleTaxonomy(cwe=(501,), owasp=("A04:2021", "API10:2023"), attack=("T1199", "T1190")),
260
+ "service-trust-chain": RuleTaxonomy(cwe=(501,), owasp=("A04:2021", "API10:2023"), asvs=("V1.2.2",), attack=("T1199", "T1190")),
261
+ "framework-sink-chain": RuleTaxonomy(cwe=(20,), owasp=("A03:2021",), asvs=("V5.1.3",), attack=("T1190",)),
262
+ "vulnerable-dependency": RuleTaxonomy(cwe=(1395,), owasp=("A06:2021",), asvs=("V14.2.1",), attack=("T1190", "T1195.001")),
263
+ "bola-modify": RuleTaxonomy(cwe=(639,), owasp=("A01:2021", "API1:2023"), asvs=("V4.2.1",), attack=("T1190",)),
264
+ "bola-read": RuleTaxonomy(cwe=(639,), owasp=("A01:2021", "API1:2023"), asvs=("V4.2.1",), attack=("T1190",)),
265
+ "unauth-state-change": RuleTaxonomy(cwe=(306,), owasp=("A07:2021", "API2:2023"), asvs=("V1.2.2", "V3.7.1"), attack=("T1190",)),
266
+ "bfla-privileged-route": RuleTaxonomy(cwe=(285,), owasp=("A01:2021", "API5:2023"), asvs=("V4.1.3",), attack=("T1068",)),
267
+ # Taint sinks (#68); speculative-<kind> inherits these
268
+ "subprocess-shell": RuleTaxonomy(cwe=(78,), owasp=("A03:2021",), asvs=("V5.3.8",), attack=("T1059", "T1190")),
269
+ "eval": RuleTaxonomy(cwe=(95, 94), owasp=("A03:2021",), asvs=("V5.2.4",), attack=("T1059", "T1190")),
270
+ "exec": RuleTaxonomy(cwe=(94,), owasp=("A03:2021",), asvs=("V5.2.4",), attack=("T1059", "T1190")),
271
+ "unsafe-deserialization": RuleTaxonomy(cwe=(502,), owasp=("A08:2021",), asvs=("V5.5.3", "V1.5.2"), attack=("T1059", "T1190")),
272
+ "ssti": RuleTaxonomy(cwe=(1336, 94), owasp=("A03:2021",), asvs=("V5.2.5",), attack=("T1059", "T1190")),
273
+ "ssrf": RuleTaxonomy(cwe=(918,), owasp=("A10:2021", "API7:2023"), asvs=("V5.2.6", "V12.6.1"), attack=("T1190", "T1552.005")),
274
+ "nosql-injection": RuleTaxonomy(cwe=(943,), owasp=("A03:2021",), asvs=("V5.3.4",), attack=("T1190",)),
275
+ # Sink catalog expansion (#240)
276
+ "zip-slip": RuleTaxonomy(cwe=(22,), owasp=("A01:2021",), asvs=("V12.3.1",), attack=("T1190",)),
277
+ "code-injection": RuleTaxonomy(cwe=(94,), owasp=("A03:2021",), asvs=("V5.2.4",), attack=("T1059", "T1190")),
278
+ "expression-injection": RuleTaxonomy(cwe=(917, 94), owasp=("A03:2021",), attack=("T1059", "T1190")),
279
+ "jndi-injection": RuleTaxonomy(cwe=(74, 917), owasp=("A03:2021",), attack=("T1059", "T1190")),
280
+ "ldap-injection": RuleTaxonomy(cwe=(90,), owasp=("A03:2021",), asvs=("V5.3.7",), attack=("T1190",)),
281
+ "xpath-injection": RuleTaxonomy(cwe=(643,), owasp=("A03:2021",), asvs=("V5.3.10",), attack=("T1190",)),
282
+ "header-injection": RuleTaxonomy(cwe=(113,), owasp=("A03:2021",), attack=("T1190",)),
283
+ "regex-injection": RuleTaxonomy(cwe=(1333,), owasp=("A03:2021",), attack=("T1499.004",)),
284
+ # Flow-confirmed taint findings (#239)
285
+ "sql-injection": RuleTaxonomy(cwe=(89,), owasp=("A03:2021",), asvs=("V5.3.4", "V5.3.5"), attack=("T1190",)),
286
+ "path-traversal": RuleTaxonomy(cwe=(22,), owasp=("A01:2021",), asvs=("V12.3.1",), attack=("T1190",)),
287
+ "open-redirect": RuleTaxonomy(cwe=(601,), owasp=("A01:2021",), asvs=("V5.1.5",), attack=("T1204.001", "T1566.002")),
288
+ # Crypto (#70)
289
+ "weak-password-hash": RuleTaxonomy(cwe=(916, 328), owasp=("A02:2021",), asvs=("V2.4.1",), attack=("T1110.002",)),
290
+ "weak-cipher": RuleTaxonomy(cwe=(327,), owasp=("A02:2021",), asvs=("V6.2.2", "V6.2.5"), attack=("T1600",)),
291
+ "ecb-mode": RuleTaxonomy(cwe=(327,), owasp=("A02:2021",), asvs=("V6.2.5",), attack=("T1600",)),
292
+ "static-iv-salt": RuleTaxonomy(cwe=(1204, 760), owasp=("A02:2021",), attack=("T1600",)),
293
+ "insecure-random": RuleTaxonomy(cwe=(338,), owasp=("A02:2021",), asvs=("V6.3.1",), attack=("T1600",)),
294
+ "insecure-tls": RuleTaxonomy(cwe=(295,), owasp=("A07:2021", "A02:2021"), asvs=("V9.2.1",), attack=("T1557",)),
295
+ # Web hardening (#71)
296
+ "cors-wildcard-credentials": RuleTaxonomy(cwe=(942,), owasp=("A05:2021", "API8:2023"), asvs=("V14.5.3",), attack=("T1539",)),
297
+ "cors-wildcard-origin": RuleTaxonomy(cwe=(942,), owasp=("A05:2021", "API8:2023"), asvs=("V14.5.3",), attack=("T1190",)),
298
+ "cors-untrusted-origin": RuleTaxonomy(cwe=(346, 942), owasp=("A05:2021", "API8:2023"), asvs=("V14.5.3",), attack=("T1539",)),
299
+ "csrf-unprotected-session": RuleTaxonomy(cwe=(352,), owasp=("A01:2021",), asvs=("V4.2.2", "V3.4.3", "V13.2.3"), attack=("T1190",)),
300
+ "csrf-disabled": RuleTaxonomy(cwe=(352,), owasp=("A01:2021",), asvs=("V4.2.2", "V13.2.3"), attack=("T1190",)),
301
+ "insecure-cookie": RuleTaxonomy(cwe=(614, 1004), owasp=("A05:2021",), asvs=("V3.4.1", "V3.4.2"), attack=("T1539",)),
302
+ "weak-csp": RuleTaxonomy(cwe=(693,), owasp=("A05:2021",), asvs=("V14.4.3",), attack=("T1190", "T1059.007")),
303
+ "debug-enabled": RuleTaxonomy(cwe=(489, 215), owasp=("A05:2021", "API8:2023"), asvs=("V14.3.2",), attack=("T1190",)),
304
+ # Code weaknesses (#77)
305
+ "prototype-pollution": RuleTaxonomy(cwe=(1321,), owasp=("A08:2021",), attack=("T1190", "T1059.007")),
306
+ "mass-assignment": RuleTaxonomy(cwe=(915,), owasp=("A08:2021", "API3:2023"), asvs=("V5.1.2",), attack=("T1190",)),
307
+ "jwt-weakness": RuleTaxonomy(cwe=(347,), owasp=("A02:2021", "API2:2023"), asvs=("V3.5.3",), attack=("T1190", "T1606")),
308
+ "jwt-alg-confusion": RuleTaxonomy(cwe=(347, 327), owasp=("A02:2021", "API2:2023"), asvs=("V3.5.3",), attack=("T1606",)),
309
+ "jwt-unverified-decode": RuleTaxonomy(cwe=(345, 347), owasp=("A07:2021", "API2:2023"), asvs=("V3.5.3",), attack=("T1606",)),
310
+ "jwt-claims-unchecked": RuleTaxonomy(cwe=(613,), owasp=("A07:2021", "API2:2023"), asvs=("V3.5.3", "V3.3.1"), attack=("T1550.001",)),
311
+ "jwt-header-trust": RuleTaxonomy(cwe=(345, 22, 89, 918), owasp=("A07:2021", "API2:2023"), asvs=("V3.5.3",), attack=("T1606",)),
312
+ "jwt-weak-secret": RuleTaxonomy(cwe=(798, 326), owasp=("A02:2021", "API2:2023"), asvs=("V3.5.2", "V2.10.4"), attack=("T1606", "T1110.002")),
313
+ "oauth-missing-state": RuleTaxonomy(cwe=(352,), owasp=("A01:2021", "API2:2023"), attack=("T1528",)),
314
+ "oauth-implicit-flow": RuleTaxonomy(cwe=(522,), owasp=("A07:2021", "API2:2023"), attack=("T1528",)),
315
+ "oauth-redirect-uri-loose": RuleTaxonomy(cwe=(601, 346), owasp=("A07:2021", "API2:2023"), attack=("T1528",)),
316
+ "oauth-missing-pkce": RuleTaxonomy(cwe=(345,), owasp=("A07:2021", "API2:2023"), attack=("T1528",)),
317
+ "session-fixation": RuleTaxonomy(cwe=(384,), owasp=("A07:2021",), asvs=("V3.2.1",), attack=("T1539",)),
318
+ "session-weak-secret": RuleTaxonomy(cwe=(798, 326), owasp=("A02:2021", "A07:2021"), attack=("T1606", "T1110.002")),
319
+ "xxe": RuleTaxonomy(cwe=(611,), owasp=("A05:2021",), asvs=("V5.5.2",), attack=("T1190",)),
320
+ "redos": RuleTaxonomy(cwe=(1333,), owasp=("API4:2023",), attack=("T1499.004",)),
321
+ "insecure-upload": RuleTaxonomy(cwe=(22, 434), owasp=("A01:2021", "A04:2021"), asvs=("V12.3.1", "V12.2.1"), attack=("T1190", "T1505.003")),
322
+ "graphql-exposure": RuleTaxonomy(cwe=(200,), owasp=("A05:2021", "API8:2023"), attack=("T1190",)),
323
+ "graphql-no-query-limits": RuleTaxonomy(cwe=(770,), owasp=("API4:2023",), asvs=("V13.4.1",), attack=("T1499.003",)),
324
+ "graphql-batching": RuleTaxonomy(cwe=(770, 307), owasp=("API4:2023", "API2:2023"), asvs=("V13.4.1", "V2.2.1"), attack=("T1110", "T1499.003")),
325
+ "prompt-injection-attempt": RuleTaxonomy(cwe=(1427,), owasp=("LLM01:2025",), attack=("T1027",)),
326
+ # Native C/C++ sinks (#189). The OWASP Top 10 has no memory-safety
327
+ # category; A03 (Injection, which carries CWE-20 improper input
328
+ # validation) is the closest fit. ASVS V5.4 is "Memory, String, and
329
+ # Unmanaged Code".
330
+ "unbounded-copy": RuleTaxonomy(cwe=(120, 121, 242), owasp=("A03:2021",), asvs=("V5.4.1",), attack=("T1190", "T1068")),
331
+ "format-string": RuleTaxonomy(cwe=(134,), owasp=("A03:2021",), asvs=("V5.4.2",), attack=("T1190", "T1068")),
332
+ "native-command-exec": RuleTaxonomy(cwe=(78,), owasp=("A03:2021",), asvs=("V5.3.8",), attack=("T1059.004", "T1190")),
333
+ "tainted-copy-length": RuleTaxonomy(cwe=(805, 787), owasp=("A03:2021",), asvs=("V5.4.1",), attack=("T1190", "T1068")),
334
+ "alloc-size-overflow": RuleTaxonomy(cwe=(680, 190), owasp=("A03:2021",), asvs=("V5.4.3",), attack=("T1190", "T1068")),
335
+ # CI workflows (#142)
336
+ "script-injection": RuleTaxonomy(cwe=(78, 94), owasp=("A03:2021", "A08:2021"), attack=("T1059.004", "T1195.002")),
337
+ "pr-target-checkout": RuleTaxonomy(cwe=(829,), owasp=("A08:2021",), asvs=("V12.3.6",), attack=("T1195.002",)),
338
+ "unpinned-action": RuleTaxonomy(cwe=(829, 1357), owasp=("A08:2021",), asvs=("V14.2.4",), attack=("T1195.001",)),
339
+ "secret-in-run": RuleTaxonomy(cwe=(532, 214), owasp=("A09:2021",), asvs=("V7.1.1",), attack=("T1552",)),
340
+ "broad-permissions": RuleTaxonomy(cwe=(250,), owasp=("A01:2021",), attack=("T1078",)),
341
+ "self-hosted-pr": RuleTaxonomy(cwe=(829,), owasp=("A08:2021",), attack=("T1195.002", "T1584.004")),
342
+ # CI workflows, round 2 (#246)
343
+ "workflow-run-artifact-poisoning": RuleTaxonomy(cwe=(829,), owasp=("A08:2021",), asvs=("V10.3.2", "V12.3.6"), attack=("T1195.002",)),
344
+ "issue-comment-pr-checkout": RuleTaxonomy(cwe=(829,), owasp=("A08:2021",), asvs=("V12.3.6",), attack=("T1195.002",)),
345
+ "github-script-injection": RuleTaxonomy(cwe=(94,), owasp=("A03:2021", "A08:2021"), asvs=("V5.2.4",), attack=("T1059.007", "T1195.002")),
346
+ "github-env-injection": RuleTaxonomy(cwe=(94, 15), owasp=("A03:2021", "A08:2021"), attack=("T1574", "T1195.002")),
347
+ "default-token-permissions": RuleTaxonomy(cwe=(250,), owasp=("A01:2021",), attack=("T1078",)),
348
+ "oidc-on-untrusted-trigger": RuleTaxonomy(cwe=(250,), owasp=("A01:2021",), attack=("T1078.004",)),
349
+ "secrets-inherit": RuleTaxonomy(cwe=(250,), owasp=("A01:2021",), attack=("T1552",)),
350
+ "checkout-persist-credentials": RuleTaxonomy(cwe=(522,), owasp=("A04:2021",), attack=("T1552.001",)),
351
+ "cache-poisoning-pr-target": RuleTaxonomy(cwe=(349,), owasp=("A08:2021",), attack=("T1195.002",)),
352
+ "docker-action-unpinned": RuleTaxonomy(cwe=(829, 1357), owasp=("A08:2021",), asvs=("V14.2.4",), attack=("T1195.001",)),
353
+ "curl-pipe-shell": RuleTaxonomy(cwe=(494,), owasp=("A08:2021",), asvs=("V10.3.2",), attack=("T1105", "T1195.002")),
354
+ # Dependency supply-chain risk (#247).
355
+ "dependency-confusion": RuleTaxonomy(cwe=(427, 829), owasp=("A08:2021", "A06:2021"), asvs=("V14.2.4",), attack=("T1195.001",)),
356
+ "typosquat-candidate": RuleTaxonomy(cwe=(1357, 829), owasp=("A06:2021", "A08:2021"), asvs=("V14.2.4",), attack=("T1195.001",)),
357
+ "mutable-vcs-dependency": RuleTaxonomy(cwe=(494, 829), owasp=("A08:2021", "A06:2021"), asvs=("V14.2.4",), attack=("T1195.001",)),
358
+ "install-script": RuleTaxonomy(cwe=(829, 494), owasp=("A08:2021",), attack=("T1195.001", "T1059")),
359
+ "unlocked-manifest": RuleTaxonomy(cwe=(353, 494), owasp=("A08:2021", "A06:2021"), asvs=("V14.2.4",), attack=("T1195.001",)),
360
+ "insecure-registry": RuleTaxonomy(cwe=(319, 295, 494), owasp=("A02:2021", "A08:2021"), attack=("T1557", "T1195.001")),
361
+ # Cloud / IaC / container misconfigurations (terraform#4, iac#1).
362
+ "iac-module-unpinned": RuleTaxonomy(cwe=(829, 494), owasp=("A08:2021",), asvs=("V14.2.4",), attack=("T1195.001",)),
363
+ "public-network-access": RuleTaxonomy(cwe=(668, 284), owasp=("A05:2021", "A01:2021"), attack=("T1190", "T1133")),
364
+ "public-cluster-endpoint": RuleTaxonomy(cwe=(668, 284), owasp=("A05:2021",), attack=("T1133", "T1190")),
365
+ "plaintext-listener": RuleTaxonomy(cwe=(319,), owasp=("A02:2021",), asvs=("V9.1.1",), attack=("T1557",)),
366
+ "api-no-authorizer": RuleTaxonomy(cwe=(306,), owasp=("A07:2021", "API2:2023"), attack=("T1190",)),
367
+ "unencrypted-storage": RuleTaxonomy(cwe=(311,), owasp=("A02:2021",), asvs=("V6.1.1",), attack=("T1530",)),
368
+ "public-data-access": RuleTaxonomy(cwe=(732, 200), owasp=("A01:2021",), attack=("T1530",)),
369
+ "public-invoker": RuleTaxonomy(cwe=(284, 306), owasp=("A01:2021",), attack=("T1190",)),
370
+ "privileged-container": RuleTaxonomy(cwe=(250,), owasp=("A05:2021",), attack=("T1611",)),
371
+ "privilege-escalation-allowed": RuleTaxonomy(cwe=(269, 250), owasp=("A05:2021",), attack=("T1548.001",)),
372
+ "container-runs-as-root": RuleTaxonomy(cwe=(250,), owasp=("A05:2021",), attack=("T1611",)),
373
+ "missing-security-context": RuleTaxonomy(cwe=(1188, 250), owasp=("A05:2021",), attack=("T1611",)),
374
+ "host-path-mount": RuleTaxonomy(cwe=(668, 250), owasp=("A05:2021",), attack=("T1611",)),
375
+ "host-namespace-shared": RuleTaxonomy(cwe=(668, 250), owasp=("A05:2021",), attack=("T1611",)),
376
+ "dangerous-capability": RuleTaxonomy(cwe=(250,), owasp=("A05:2021",), attack=("T1611",)),
377
+ "security-profile-disabled": RuleTaxonomy(cwe=(693,), owasp=("A05:2021",), attack=("T1611",)),
378
+ "exposed-service": RuleTaxonomy(cwe=(668,), owasp=("A05:2021",), attack=("T1190", "T1133")),
379
+ "unpinned-container-image": RuleTaxonomy(cwe=(829, 494), owasp=("A08:2021", "A06:2021"), attack=("T1195.002",)),
380
+ "dockerfile-remote-add": RuleTaxonomy(cwe=(494,), owasp=("A08:2021",), asvs=("V10.3.2",), attack=("T1105", "T1195.002")),
381
+ "dockerfile-curl-pipe-shell": RuleTaxonomy(cwe=(494,), owasp=("A08:2021",), asvs=("V10.3.2",), attack=("T1105", "T1195.002")),
382
+ "rbac-wildcard": RuleTaxonomy(cwe=(269, 732), owasp=("A01:2021",), attack=("T1078", "T1068")),
383
+ "rbac-escalation": RuleTaxonomy(cwe=(269,), owasp=("A01:2021",), attack=("T1098", "T1068", "T1609")),
384
+ "rbac-secret-access": RuleTaxonomy(cwe=(522, 732), owasp=("A01:2021",), attack=("T1552.007",)),
385
+ "cluster-admin-binding": RuleTaxonomy(cwe=(269, 250), owasp=("A01:2021",), attack=("T1078", "T1098")),
386
+ # App attack surface from platform plugins (#188, #190)
387
+ "apple-ats-insecure-loads": RuleTaxonomy(cwe=(319,), owasp=("A02:2021",), asvs=("V9.1.1",), attack=("T1557",)),
388
+ "apple-custom-url-scheme": RuleTaxonomy(cwe=(939,), owasp=("A01:2021",), attack=("T1204.001",)),
389
+ "apple-webview-js-injection": RuleTaxonomy(cwe=(94, 79), owasp=("A03:2021",), asvs=("V5.3.3",), attack=("T1059.007",)),
390
+ "apple-insecure-secret-storage": RuleTaxonomy(cwe=(312, 922), owasp=("A04:2021",), attack=("T1552.001",)),
391
+ "apple-pasteboard-secret": RuleTaxonomy(cwe=(200,), owasp=("A01:2021",), attack=("T1115",)),
392
+ "apple-hardened-runtime-exception": RuleTaxonomy(cwe=(693, 114), owasp=("A05:2021",), attack=("T1574",)),
393
+ "apple-debuggable-entitlement": RuleTaxonomy(cwe=(489,), owasp=("A05:2021",), attack=("T1055",)),
394
+ "apple-xpc-unvalidated-peer": RuleTaxonomy(cwe=(306, 284), owasp=("A07:2021", "A01:2021"), attack=("T1068",)),
395
+ "apple-dynamic-dispatch": RuleTaxonomy(cwe=(470, 114), owasp=("A03:2021",), attack=("T1129",)),
396
+ # Anomalies (#78)
397
+ "auth-outlier": RuleTaxonomy(cwe=(862,), owasp=("A01:2021", "API5:2023"), asvs=("V4.1.3", "V13.1.4"), attack=("T1190",)),
398
+ "validation-outlier": RuleTaxonomy(cwe=(20,), owasp=("A03:2021",), asvs=("V5.1.3",), attack=("T1190",)),
399
+ "method-outlier": RuleTaxonomy(cwe=(749,), owasp=("A01:2021", "API5:2023"), asvs=("V13.2.1",), attack=("T1190",)),
400
+ "invariant-violation": RuleTaxonomy(cwe=(693,), owasp=("A04:2021",), attack=("T1190",)),
401
+ }
402
+
403
+ _SPECULATIVE = "speculative-"
404
+ _ASVS_ID = re.compile(r"V\d{1,2}\.\d{1,2}\.\d{1,2}")
405
+
406
+
407
+ def taxonomy_for(rule_id: str | None) -> RuleTaxonomy | None:
408
+ """The registered taxonomy for ``rule_id``, or ``None``. A
409
+ ``speculative-<rule>`` id resolves to its base rule."""
410
+ if not rule_id:
411
+ return None
412
+ entry = RULE_TAXONOMY.get(rule_id)
413
+ if entry is None and rule_id.startswith(_SPECULATIVE):
414
+ entry = RULE_TAXONOMY.get(rule_id[len(_SPECULATIVE):])
415
+ return entry
416
+
417
+
418
+ def register(rule_id: str, entry: RuleTaxonomy) -> None:
419
+ """Register (or replace) a rule's taxonomy at runtime, for plugins that
420
+ emit their own rule ids. Validates the ids against the catalogs here."""
421
+ problems = validate_entry(entry)
422
+ if problems:
423
+ raise ValueError(f"invalid taxonomy for {rule_id!r}: {'; '.join(problems)}")
424
+ RULE_TAXONOMY[rule_id] = entry
425
+
426
+
427
+ def validate_entry(entry: RuleTaxonomy) -> list[str]:
428
+ """Problems with an entry: missing CWE/OWASP, or ids unknown to the
429
+ catalogs above (add the id and its name there first)."""
430
+ problems: list[str] = []
431
+ if not entry.cwe:
432
+ problems.append("no CWE")
433
+ if not entry.owasp:
434
+ problems.append("no OWASP category")
435
+ problems += [f"CWE-{c} not in CWE_NAMES" for c in entry.cwe if c not in CWE_NAMES]
436
+ problems += [f"{o} not in OWASP_CATEGORIES" for o in entry.owasp if o not in OWASP_CATEGORIES]
437
+ problems += [f"{t} not in ATTACK_TECHNIQUES" for t in entry.attack if t not in ATTACK_TECHNIQUES]
438
+ problems += [f"malformed ASVS id {a}" for a in entry.asvs if not _ASVS_ID.fullmatch(a)]
439
+ return problems
440
+
441
+
442
+ def taxonomy_label(rule_id: str | None) -> str:
443
+ """Short inline label for Markdown, e.g. ``CWE-918 · A10:2021 · API7:2023``.
444
+ Empty when the rule is unregistered."""
445
+ entry = taxonomy_for(rule_id)
446
+ if entry is None:
447
+ return ""
448
+ return " · ".join([*entry.cwe_ids[:2], *entry.owasp[:2]])
449
+
450
+
451
+ __all__ = [
452
+ "ATTACK_TECHNIQUES",
453
+ "CWE_NAMES",
454
+ "INFORMATIONAL_RULES",
455
+ "OWASP_CATEGORIES",
456
+ "RULE_TAXONOMY",
457
+ "RuleTaxonomy",
458
+ "attack_url",
459
+ "cwe_tag",
460
+ "cwe_url",
461
+ "register",
462
+ "taxonomy_for",
463
+ "taxonomy_label",
464
+ "validate_entry",
465
+ ]