kekkai-cli 1.0.5__py3-none-any.whl → 1.1.1__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 (53) hide show
  1. kekkai/cli.py +789 -19
  2. kekkai/compliance/__init__.py +68 -0
  3. kekkai/compliance/hipaa.py +235 -0
  4. kekkai/compliance/mappings.py +136 -0
  5. kekkai/compliance/owasp.py +517 -0
  6. kekkai/compliance/owasp_agentic.py +267 -0
  7. kekkai/compliance/pci_dss.py +205 -0
  8. kekkai/compliance/soc2.py +209 -0
  9. kekkai/dojo.py +91 -14
  10. kekkai/dojo_import.py +9 -1
  11. kekkai/fix/__init__.py +47 -0
  12. kekkai/fix/audit.py +278 -0
  13. kekkai/fix/differ.py +427 -0
  14. kekkai/fix/engine.py +500 -0
  15. kekkai/fix/prompts.py +251 -0
  16. kekkai/output.py +10 -12
  17. kekkai/report/__init__.py +41 -0
  18. kekkai/report/compliance_matrix.py +98 -0
  19. kekkai/report/generator.py +365 -0
  20. kekkai/report/html.py +69 -0
  21. kekkai/report/pdf.py +63 -0
  22. kekkai/report/unified.py +226 -0
  23. kekkai/scanners/container.py +33 -3
  24. kekkai/scanners/gitleaks.py +3 -1
  25. kekkai/scanners/semgrep.py +1 -1
  26. kekkai/scanners/trivy.py +1 -1
  27. kekkai/threatflow/model_adapter.py +143 -1
  28. kekkai/triage/__init__.py +54 -1
  29. kekkai/triage/loader.py +196 -0
  30. kekkai_cli-1.1.1.dist-info/METADATA +379 -0
  31. {kekkai_cli-1.0.5.dist-info → kekkai_cli-1.1.1.dist-info}/RECORD +34 -33
  32. {kekkai_cli-1.0.5.dist-info → kekkai_cli-1.1.1.dist-info}/entry_points.txt +0 -1
  33. {kekkai_cli-1.0.5.dist-info → kekkai_cli-1.1.1.dist-info}/top_level.txt +0 -1
  34. kekkai_cli-1.0.5.dist-info/METADATA +0 -135
  35. portal/__init__.py +0 -19
  36. portal/api.py +0 -155
  37. portal/auth.py +0 -103
  38. portal/enterprise/__init__.py +0 -32
  39. portal/enterprise/audit.py +0 -435
  40. portal/enterprise/licensing.py +0 -342
  41. portal/enterprise/rbac.py +0 -276
  42. portal/enterprise/saml.py +0 -595
  43. portal/ops/__init__.py +0 -53
  44. portal/ops/backup.py +0 -553
  45. portal/ops/log_shipper.py +0 -469
  46. portal/ops/monitoring.py +0 -517
  47. portal/ops/restore.py +0 -469
  48. portal/ops/secrets.py +0 -408
  49. portal/ops/upgrade.py +0 -591
  50. portal/tenants.py +0 -340
  51. portal/uploads.py +0 -259
  52. portal/web.py +0 -384
  53. {kekkai_cli-1.0.5.dist-info → kekkai_cli-1.1.1.dist-info}/WHEEL +0 -0
@@ -0,0 +1,267 @@
1
+ """OWASP Top 10 for Agentic AI mapping for security findings.
2
+
3
+ Maps findings to OWASP Agentic AI Top 10 categories based on rule patterns.
4
+ Reference: https://genai.owasp.org/initiatives/agentic-security-initiative/
5
+
6
+ Released: December 2025 at Black Hat Europe
7
+
8
+ This framework addresses security risks specific to autonomous AI agents
9
+ that make decisions and take actions in real-world environments.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from dataclasses import dataclass
15
+ from typing import TYPE_CHECKING
16
+
17
+ from .mappings import FrameworkControl
18
+
19
+ if TYPE_CHECKING:
20
+ from kekkai.scanners.base import Finding
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class OWASPAgenticCategory:
25
+ """OWASP Agentic AI Top 10 category definition."""
26
+
27
+ id: str
28
+ name: str
29
+ description: str
30
+ rule_patterns: tuple[str, ...]
31
+ scanner_types: tuple[str, ...] # Only apply to these scanner types
32
+
33
+
34
+ # OWASP Top 10 for Agentic AI (December 2025)
35
+ OWASP_AGENTIC_TOP_10: dict[str, OWASPAgenticCategory] = {
36
+ "AA01": OWASPAgenticCategory(
37
+ id="AA01:2025",
38
+ name="Agent Goal Hijack",
39
+ description=(
40
+ "Attackers alter an agent's objectives through malicious content, "
41
+ "prompt injection, or manipulated inputs causing the agent to "
42
+ "act against the user's intent."
43
+ ),
44
+ rule_patterns=(
45
+ "goal-hijack",
46
+ "prompt-injection",
47
+ "jailbreak",
48
+ "objective-manipulation",
49
+ "instruction-override",
50
+ "system-prompt",
51
+ ),
52
+ scanner_types=("llm", "ai", "genai", "agent", "semgrep"),
53
+ ),
54
+ "AA02": OWASPAgenticCategory(
55
+ id="AA02:2025",
56
+ name="Tool Misuse and Exploitation",
57
+ description=(
58
+ "Agents use legitimate tools in unsafe or unintended ways, "
59
+ "potentially causing harmful actions through manipulation "
60
+ "of tool parameters or abuse of tool capabilities."
61
+ ),
62
+ rule_patterns=(
63
+ "tool-misuse",
64
+ "tool-abuse",
65
+ "function-call",
66
+ "api-abuse",
67
+ "unsafe-tool",
68
+ "tool-injection",
69
+ ),
70
+ scanner_types=("llm", "ai", "genai", "agent", "semgrep"),
71
+ ),
72
+ "AA03": OWASPAgenticCategory(
73
+ id="AA03:2025",
74
+ name="Identity and Privilege Abuse",
75
+ description=(
76
+ "Agents inherit or escalate high-privilege credentials, "
77
+ "risking unauthorized access to sensitive systems, data, "
78
+ "or operations beyond their intended scope."
79
+ ),
80
+ rule_patterns=(
81
+ "privilege-escalation",
82
+ "identity-abuse",
83
+ "credential-inheritance",
84
+ "over-privileged",
85
+ "least-privilege",
86
+ "agent-permission",
87
+ ),
88
+ scanner_types=("llm", "ai", "genai", "agent", "semgrep"),
89
+ ),
90
+ "AA04": OWASPAgenticCategory(
91
+ id="AA04:2025",
92
+ name="Agentic Supply Chain Vulnerabilities",
93
+ description=(
94
+ "Compromised external components, plugins, or third-party "
95
+ "agents can affect the security and behavior of the entire "
96
+ "agentic system."
97
+ ),
98
+ rule_patterns=(
99
+ "agent-supply-chain",
100
+ "plugin-vulnerability",
101
+ "external-agent",
102
+ "third-party-agent",
103
+ "model-supply-chain",
104
+ "compromised-plugin",
105
+ ),
106
+ scanner_types=("llm", "ai", "genai", "agent", "semgrep", "trivy"),
107
+ ),
108
+ "AA05": OWASPAgenticCategory(
109
+ id="AA05:2025",
110
+ name="Unexpected Code Execution",
111
+ description=(
112
+ "Agents generate or execute code unsafely, potentially "
113
+ "running malicious code, accessing unauthorized resources, "
114
+ "or causing system compromise."
115
+ ),
116
+ rule_patterns=(
117
+ "code-execution",
118
+ "code-generation",
119
+ "unsafe-eval",
120
+ "dynamic-execution",
121
+ "sandbox-escape",
122
+ "code-injection",
123
+ ),
124
+ scanner_types=("llm", "ai", "genai", "agent", "semgrep"),
125
+ ),
126
+ "AA06": OWASPAgenticCategory(
127
+ id="AA06:2025",
128
+ name="Memory and Context Poisoning",
129
+ description=(
130
+ "Attackers corrupt an agent's memory systems or context "
131
+ "window to influence future behavior, decisions, or outputs "
132
+ "across sessions."
133
+ ),
134
+ rule_patterns=(
135
+ "memory-poisoning",
136
+ "context-poisoning",
137
+ "context-manipulation",
138
+ "memory-injection",
139
+ "persistent-attack",
140
+ "rag-poisoning",
141
+ ),
142
+ scanner_types=("llm", "ai", "genai", "agent"),
143
+ ),
144
+ "AA07": OWASPAgenticCategory(
145
+ id="AA07:2025",
146
+ name="Insecure Inter-Agent Communication",
147
+ description=(
148
+ "Risks of spoofing, tampering, and man-in-the-middle attacks "
149
+ "in multi-agent systems due to weak authentication or "
150
+ "encryption between agents."
151
+ ),
152
+ rule_patterns=(
153
+ "inter-agent",
154
+ "multi-agent",
155
+ "agent-communication",
156
+ "agent-spoofing",
157
+ "agent-tampering",
158
+ "agent-auth",
159
+ ),
160
+ scanner_types=("llm", "ai", "genai", "agent"),
161
+ ),
162
+ "AA08": OWASPAgenticCategory(
163
+ id="AA08:2025",
164
+ name="Cascading Failures",
165
+ description=(
166
+ "Small errors or failures in one part of an agentic system "
167
+ "can propagate and cause larger, widespread failures across "
168
+ "the entire system."
169
+ ),
170
+ rule_patterns=(
171
+ "cascading-failure",
172
+ "error-propagation",
173
+ "fault-tolerance",
174
+ "failure-isolation",
175
+ "circuit-breaker",
176
+ "retry-storm",
177
+ ),
178
+ scanner_types=("llm", "ai", "genai", "agent", "semgrep"),
179
+ ),
180
+ "AA09": OWASPAgenticCategory(
181
+ id="AA09:2025",
182
+ name="Human-Agent Trust Exploitation",
183
+ description=(
184
+ "Users place excessive trust in agent recommendations, "
185
+ "leading to social engineering attacks, manipulation, "
186
+ "or harmful decisions based on agent outputs."
187
+ ),
188
+ rule_patterns=(
189
+ "trust-exploitation",
190
+ "over-trust",
191
+ "social-engineering",
192
+ "manipulation",
193
+ "deception",
194
+ "human-override",
195
+ ),
196
+ scanner_types=("llm", "ai", "genai", "agent"),
197
+ ),
198
+ "AA10": OWASPAgenticCategory(
199
+ id="AA10:2025",
200
+ name="Rogue Agents",
201
+ description=(
202
+ "Compromised agents act maliciously while appearing legitimate, "
203
+ "potentially exfiltrating data, causing damage, or subverting "
204
+ "system controls."
205
+ ),
206
+ rule_patterns=(
207
+ "rogue-agent",
208
+ "compromised-agent",
209
+ "malicious-agent",
210
+ "agent-takeover",
211
+ "agent-backdoor",
212
+ "agent-exfiltration",
213
+ ),
214
+ scanner_types=("llm", "ai", "genai", "agent"),
215
+ ),
216
+ }
217
+
218
+
219
+ def map_to_owasp_agentic(finding: Finding) -> list[FrameworkControl]:
220
+ """Map a finding to OWASP Agentic AI Top 10 categories.
221
+
222
+ Agentic AI mappings are primarily pattern-based since there are
223
+ few established CWEs for these emerging risks. Mappings are also
224
+ filtered by scanner type to avoid false positives.
225
+ """
226
+ controls: list[FrameworkControl] = []
227
+
228
+ # Check rule_id patterns
229
+ rule_id_lower = (finding.rule_id or "").lower()
230
+ title_lower = finding.title.lower()
231
+ description_lower = finding.description.lower()
232
+ scanner_lower = finding.scanner.lower()
233
+
234
+ for category in OWASP_AGENTIC_TOP_10.values():
235
+ matched = False
236
+
237
+ # Check if scanner type is relevant for this category
238
+ scanner_relevant = any(st in scanner_lower for st in category.scanner_types)
239
+
240
+ # For generic scanners like semgrep, also check if the finding
241
+ # is related to AI/LLM based on content
242
+ if not scanner_relevant:
243
+ ai_keywords = ("llm", "agent", "ai", "model", "prompt", "genai")
244
+ if any(kw in title_lower or kw in description_lower for kw in ai_keywords):
245
+ scanner_relevant = True
246
+
247
+ if not scanner_relevant:
248
+ continue
249
+
250
+ # Match by rule pattern
251
+ for pattern in category.rule_patterns:
252
+ if pattern in rule_id_lower or pattern in title_lower or pattern in description_lower:
253
+ matched = True
254
+ break
255
+
256
+ if matched:
257
+ controls.append(
258
+ FrameworkControl(
259
+ framework="OWASP-Agentic",
260
+ control_id=category.id,
261
+ title=category.name,
262
+ description=category.description,
263
+ requirement_level="required",
264
+ )
265
+ )
266
+
267
+ return controls
@@ -0,0 +1,205 @@
1
+ """PCI-DSS v4.0 control mapping for security findings.
2
+
3
+ Maps findings to PCI-DSS requirements based on CWE IDs and rule patterns.
4
+ Reference: https://docs-prv.pcisecuritystandards.org/PCI%20DSS/Standard/PCI-DSS-v4_0.pdf
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass
10
+ from typing import TYPE_CHECKING
11
+
12
+ from .mappings import FrameworkControl
13
+
14
+ if TYPE_CHECKING:
15
+ from kekkai.scanners.base import Finding
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class PCIDSSControl:
20
+ """PCI-DSS v4.0 control definition."""
21
+
22
+ id: str
23
+ title: str
24
+ description: str
25
+ cwes: frozenset[int]
26
+ rule_patterns: tuple[str, ...]
27
+
28
+
29
+ # PCI-DSS v4.0 Requirements relevant to application security
30
+ PCI_DSS_CONTROLS: dict[str, PCIDSSControl] = {
31
+ "1.4": PCIDSSControl(
32
+ id="1.4",
33
+ title="Network connections between trusted and untrusted networks are controlled",
34
+ description=(
35
+ "System components that store, process, or transmit CHD are not "
36
+ "directly accessible from untrusted networks"
37
+ ),
38
+ cwes=frozenset({918, 441}), # SSRF, open redirect
39
+ rule_patterns=("ssrf", "network", "firewall"),
40
+ ),
41
+ "2.2.6": PCIDSSControl(
42
+ id="2.2.6",
43
+ title="System security parameters prevent misuse",
44
+ description="Common security parameter settings are correctly configured",
45
+ cwes=frozenset({16, 260, 315, 520, 756}),
46
+ rule_patterns=("misconfiguration", "config", "security-parameter", "hardcoded"),
47
+ ),
48
+ "3.5": PCIDSSControl(
49
+ id="3.5",
50
+ title="Primary account number (PAN) is secured wherever stored",
51
+ description="PAN is rendered unreadable using strong cryptography",
52
+ cwes=frozenset({311, 312, 319, 327, 328}),
53
+ rule_patterns=("encryption", "crypto", "storage", "pan", "card"),
54
+ ),
55
+ "4.2": PCIDSSControl(
56
+ id="4.2",
57
+ title="PAN is protected during transmission",
58
+ description=(
59
+ "Strong cryptography protects PAN during transmission over open, public networks"
60
+ ),
61
+ cwes=frozenset({319, 523, 757}),
62
+ rule_patterns=("tls", "ssl", "transmission", "transport"),
63
+ ),
64
+ "5.2": PCIDSSControl(
65
+ id="5.2",
66
+ title="Malicious software is prevented or detected and addressed",
67
+ description="Anti-malware solutions detect and address malicious software",
68
+ cwes=frozenset({94, 502, 829}), # code injection, deserialization, untrusted code
69
+ rule_patterns=("malware", "code-injection", "deserialization"),
70
+ ),
71
+ "6.2.4": PCIDSSControl(
72
+ id="6.2.4",
73
+ title="Software engineering techniques prevent or mitigate common attacks",
74
+ description=(
75
+ "Injection attacks, buffer overflows, insecure cryptographic "
76
+ "storage, etc. are prevented"
77
+ ),
78
+ cwes=frozenset({20, 74, 77, 78, 79, 89, 90, 91, 94, 120, 134, 190}),
79
+ rule_patterns=("injection", "sqli", "xss", "buffer", "overflow"),
80
+ ),
81
+ "6.3.1": PCIDSSControl(
82
+ id="6.3.1",
83
+ title="Security vulnerabilities are identified and managed",
84
+ description=(
85
+ "A process is defined for identifying security vulnerabilities "
86
+ "using reputable external sources"
87
+ ),
88
+ cwes=frozenset({937, 1035, 1104}),
89
+ rule_patterns=("cve-", "vulnerability", "outdated", "component"),
90
+ ),
91
+ "6.3.2": PCIDSSControl(
92
+ id="6.3.2",
93
+ title="An inventory of custom and third-party software is maintained",
94
+ description="Software inventory including components and dependencies",
95
+ cwes=frozenset({1104}),
96
+ rule_patterns=("dependency", "component", "sbom"),
97
+ ),
98
+ "6.5.1": PCIDSSControl(
99
+ id="6.5.1",
100
+ title="Changes to production systems follow change control procedures",
101
+ description="Development and test environments are separate from production",
102
+ cwes=frozenset({489, 540}), # debug code, sensitive info exposure
103
+ rule_patterns=("debug", "development", "test-code"),
104
+ ),
105
+ "6.5.2": PCIDSSControl(
106
+ id="6.5.2",
107
+ title="Live PANs are not used in pre-production environments",
108
+ description="Test data does not contain live PAN or sensitive auth data",
109
+ cwes=frozenset({200, 312}),
110
+ rule_patterns=("test-data", "sensitive-data", "pii"),
111
+ ),
112
+ "6.5.4": PCIDSSControl(
113
+ id="6.5.4",
114
+ title="Roles and functions are separated between production and pre-production",
115
+ description="Separation of duties between environments",
116
+ cwes=frozenset({269, 284}),
117
+ rule_patterns=("privilege", "separation", "access-control"),
118
+ ),
119
+ "7.2": PCIDSSControl(
120
+ id="7.2",
121
+ title="Access to system components and data is appropriately defined",
122
+ description="Role-based access control limits access based on need-to-know",
123
+ cwes=frozenset({264, 284, 285, 639, 862, 863}),
124
+ rule_patterns=("access-control", "authorization", "rbac", "idor"),
125
+ ),
126
+ "8.3": PCIDSSControl(
127
+ id="8.3",
128
+ title="Strong authentication for users and administrators",
129
+ description="MFA and strong password requirements",
130
+ cwes=frozenset({255, 259, 287, 307, 521, 640, 798}),
131
+ rule_patterns=("authentication", "password", "credential", "mfa", "hardcoded-password"),
132
+ ),
133
+ "8.6": PCIDSSControl(
134
+ id="8.6",
135
+ title="Use of application and system accounts is strictly managed",
136
+ description="Shared and service accounts are managed securely",
137
+ cwes=frozenset({250, 269, 522}),
138
+ rule_patterns=("service-account", "shared-credential", "privilege"),
139
+ ),
140
+ "10.3": PCIDSSControl(
141
+ id="10.3",
142
+ title="Audit logs are protected from destruction and unauthorized modifications",
143
+ description="Audit trail records cannot be altered",
144
+ cwes=frozenset({117, 532, 778}),
145
+ rule_patterns=("logging", "audit", "log-injection", "log-tampering"),
146
+ ),
147
+ "11.3.1": PCIDSSControl(
148
+ id="11.3.1",
149
+ title="External and internal vulnerabilities are managed",
150
+ description="Vulnerability scans performed at least quarterly",
151
+ cwes=frozenset({937}),
152
+ rule_patterns=("vulnerability", "scan"),
153
+ ),
154
+ }
155
+
156
+
157
+ def _extract_cwe_id(cwe_str: str | None) -> int | None:
158
+ """Extract numeric CWE ID from string."""
159
+ if not cwe_str:
160
+ return None
161
+ cwe_str = cwe_str.upper().replace("CWE-", "").replace("CWE", "")
162
+ try:
163
+ return int(cwe_str.strip())
164
+ except ValueError:
165
+ return None
166
+
167
+
168
+ def map_to_pci_dss(finding: Finding) -> list[FrameworkControl]:
169
+ """Map a finding to PCI-DSS v4.0 controls."""
170
+ controls: list[FrameworkControl] = []
171
+
172
+ cwe_id = _extract_cwe_id(finding.cwe)
173
+ rule_id_lower = (finding.rule_id or "").lower()
174
+ title_lower = finding.title.lower()
175
+
176
+ for control in PCI_DSS_CONTROLS.values():
177
+ matched = False
178
+
179
+ # Match by CWE
180
+ if cwe_id and cwe_id in control.cwes:
181
+ matched = True
182
+
183
+ # Match by rule pattern
184
+ if not matched:
185
+ for pattern in control.rule_patterns:
186
+ if pattern in rule_id_lower or pattern in title_lower:
187
+ matched = True
188
+ break
189
+
190
+ # Special case: CVEs map to 6.3.1 (vulnerability management)
191
+ if not matched and control.id == "6.3.1" and finding.cve:
192
+ matched = True
193
+
194
+ if matched:
195
+ controls.append(
196
+ FrameworkControl(
197
+ framework="PCI-DSS",
198
+ control_id=control.id,
199
+ title=control.title,
200
+ description=control.description,
201
+ requirement_level="required",
202
+ )
203
+ )
204
+
205
+ return controls
@@ -0,0 +1,209 @@
1
+ """SOC 2 Type II criteria mapping for security findings.
2
+
3
+ Maps findings to SOC 2 Trust Services Criteria based on CWE IDs and rule patterns.
4
+ Reference: https://www.aicpa.org/resources/landing/trust-services-criteria
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass
10
+ from typing import TYPE_CHECKING
11
+
12
+ from .mappings import FrameworkControl
13
+
14
+ if TYPE_CHECKING:
15
+ from kekkai.scanners.base import Finding
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class SOC2Criterion:
20
+ """SOC 2 Trust Services Criterion definition."""
21
+
22
+ id: str
23
+ title: str
24
+ description: str
25
+ category: str # Security, Availability, Processing Integrity, Confidentiality, Privacy
26
+ cwes: frozenset[int]
27
+ rule_patterns: tuple[str, ...]
28
+
29
+
30
+ # SOC 2 Trust Services Criteria relevant to application security
31
+ SOC2_CRITERIA: dict[str, SOC2Criterion] = {
32
+ # Security (Common Criteria)
33
+ "CC6.1": SOC2Criterion(
34
+ id="CC6.1",
35
+ title="Logical and Physical Access Controls",
36
+ description=(
37
+ "The entity implements logical access security software, "
38
+ "infrastructure, and architectures to protect against threats"
39
+ ),
40
+ category="Security",
41
+ cwes=frozenset({264, 284, 285, 287, 306, 639, 862, 863}),
42
+ rule_patterns=("access-control", "authorization", "authentication", "idor"),
43
+ ),
44
+ "CC6.2": SOC2Criterion(
45
+ id="CC6.2",
46
+ title="User Registration and Authorization",
47
+ description="Prior to issuing system credentials, users are registered and authorized",
48
+ category="Security",
49
+ cwes=frozenset({287, 288, 302, 307, 521}),
50
+ rule_patterns=("registration", "user-management", "credential"),
51
+ ),
52
+ "CC6.3": SOC2Criterion(
53
+ id="CC6.3",
54
+ title="Credential Lifecycle Management",
55
+ description="The entity authorizes, modifies, or removes access based on roles",
56
+ category="Security",
57
+ cwes=frozenset({255, 259, 269, 522, 613, 640, 798}),
58
+ rule_patterns=("credential", "password", "session", "privilege"),
59
+ ),
60
+ "CC6.6": SOC2Criterion(
61
+ id="CC6.6",
62
+ title="Security Events Detection",
63
+ description="The entity implements controls to prevent, detect, and act on security events",
64
+ category="Security",
65
+ cwes=frozenset({117, 223, 532, 778}),
66
+ rule_patterns=("logging", "monitoring", "detection", "audit"),
67
+ ),
68
+ "CC6.7": SOC2Criterion(
69
+ id="CC6.7",
70
+ title="Transmission Protection",
71
+ description=(
72
+ "The entity restricts transmission of confidential information "
73
+ "over communication channels"
74
+ ),
75
+ category="Security",
76
+ cwes=frozenset({319, 523, 757}),
77
+ rule_patterns=("tls", "ssl", "encryption", "transmission"),
78
+ ),
79
+ "CC6.8": SOC2Criterion(
80
+ id="CC6.8",
81
+ title="Malicious Software Prevention",
82
+ description="The entity implements controls to prevent or detect malicious software",
83
+ category="Security",
84
+ cwes=frozenset({94, 502, 829}),
85
+ rule_patterns=("malware", "injection", "deserialization"),
86
+ ),
87
+ "CC7.1": SOC2Criterion(
88
+ id="CC7.1",
89
+ title="Vulnerability Management",
90
+ description=(
91
+ "The entity uses detection and monitoring procedures to identify vulnerabilities"
92
+ ),
93
+ category="Security",
94
+ cwes=frozenset({937, 1035, 1104}),
95
+ rule_patterns=("vulnerability", "cve-", "outdated", "component"),
96
+ ),
97
+ "CC7.2": SOC2Criterion(
98
+ id="CC7.2",
99
+ title="Security Incident Response",
100
+ description=(
101
+ "The entity monitors system components for anomalies indicative of malicious acts"
102
+ ),
103
+ category="Security",
104
+ cwes=frozenset({778, 779}),
105
+ rule_patterns=("incident", "response", "anomaly"),
106
+ ),
107
+ "CC8.1": SOC2Criterion(
108
+ id="CC8.1",
109
+ title="Change Management",
110
+ description="The entity authorizes, designs, develops, configures, and implements changes",
111
+ category="Security",
112
+ cwes=frozenset({489, 540}),
113
+ rule_patterns=("change-management", "deployment", "debug"),
114
+ ),
115
+ # Confidentiality
116
+ "C1.1": SOC2Criterion(
117
+ id="C1.1",
118
+ title="Confidential Information Identification",
119
+ description="The entity identifies and maintains confidential information",
120
+ category="Confidentiality",
121
+ cwes=frozenset({200, 201, 312, 319, 359}),
122
+ rule_patterns=("sensitive-data", "pii", "secret", "confidential"),
123
+ ),
124
+ "C1.2": SOC2Criterion(
125
+ id="C1.2",
126
+ title="Confidential Information Disposal",
127
+ description="The entity disposes of confidential information to meet objectives",
128
+ category="Confidentiality",
129
+ cwes=frozenset({226, 312, 459}),
130
+ rule_patterns=("disposal", "cleanup", "retention"),
131
+ ),
132
+ # Processing Integrity
133
+ "PI1.2": SOC2Criterion(
134
+ id="PI1.2",
135
+ title="Input Validation",
136
+ description="The entity implements policies to verify input is complete and accurate",
137
+ category="Processing Integrity",
138
+ cwes=frozenset({20, 74, 77, 78, 79, 89, 91, 94}),
139
+ rule_patterns=("validation", "input", "injection", "xss", "sqli"),
140
+ ),
141
+ "PI1.4": SOC2Criterion(
142
+ id="PI1.4",
143
+ title="Output Validation",
144
+ description="The entity implements policies to verify output is complete and accurate",
145
+ category="Processing Integrity",
146
+ cwes=frozenset({79, 116}),
147
+ rule_patterns=("output", "encoding", "xss"),
148
+ ),
149
+ # Availability
150
+ "A1.2": SOC2Criterion(
151
+ id="A1.2",
152
+ title="System Recovery",
153
+ description="The entity implements policies to support system recovery",
154
+ category="Availability",
155
+ cwes=frozenset({400, 770}), # resource exhaustion
156
+ rule_patterns=("dos", "resource", "availability", "recovery"),
157
+ ),
158
+ }
159
+
160
+
161
+ def _extract_cwe_id(cwe_str: str | None) -> int | None:
162
+ """Extract numeric CWE ID from string."""
163
+ if not cwe_str:
164
+ return None
165
+ cwe_str = cwe_str.upper().replace("CWE-", "").replace("CWE", "")
166
+ try:
167
+ return int(cwe_str.strip())
168
+ except ValueError:
169
+ return None
170
+
171
+
172
+ def map_to_soc2(finding: Finding) -> list[FrameworkControl]:
173
+ """Map a finding to SOC 2 criteria."""
174
+ controls: list[FrameworkControl] = []
175
+
176
+ cwe_id = _extract_cwe_id(finding.cwe)
177
+ rule_id_lower = (finding.rule_id or "").lower()
178
+ title_lower = finding.title.lower()
179
+
180
+ for criterion in SOC2_CRITERIA.values():
181
+ matched = False
182
+
183
+ # Match by CWE
184
+ if cwe_id and cwe_id in criterion.cwes:
185
+ matched = True
186
+
187
+ # Match by rule pattern
188
+ if not matched:
189
+ for pattern in criterion.rule_patterns:
190
+ if pattern in rule_id_lower or pattern in title_lower:
191
+ matched = True
192
+ break
193
+
194
+ # CVEs map to CC7.1 (vulnerability management)
195
+ if not matched and criterion.id == "CC7.1" and finding.cve:
196
+ matched = True
197
+
198
+ if matched:
199
+ controls.append(
200
+ FrameworkControl(
201
+ framework="SOC2",
202
+ control_id=criterion.id,
203
+ title=criterion.title,
204
+ description=criterion.description,
205
+ requirement_level="required",
206
+ )
207
+ )
208
+
209
+ return controls