kekkai-cli 1.0.0__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 (90) hide show
  1. kekkai/__init__.py +7 -0
  2. kekkai/cli.py +1038 -0
  3. kekkai/config.py +403 -0
  4. kekkai/dojo.py +419 -0
  5. kekkai/dojo_import.py +213 -0
  6. kekkai/github/__init__.py +16 -0
  7. kekkai/github/commenter.py +198 -0
  8. kekkai/github/models.py +56 -0
  9. kekkai/github/sanitizer.py +112 -0
  10. kekkai/installer/__init__.py +39 -0
  11. kekkai/installer/errors.py +23 -0
  12. kekkai/installer/extract.py +161 -0
  13. kekkai/installer/manager.py +252 -0
  14. kekkai/installer/manifest.py +189 -0
  15. kekkai/installer/verify.py +86 -0
  16. kekkai/manifest.py +77 -0
  17. kekkai/output.py +218 -0
  18. kekkai/paths.py +46 -0
  19. kekkai/policy.py +326 -0
  20. kekkai/runner.py +70 -0
  21. kekkai/scanners/__init__.py +67 -0
  22. kekkai/scanners/backends/__init__.py +14 -0
  23. kekkai/scanners/backends/base.py +73 -0
  24. kekkai/scanners/backends/docker.py +178 -0
  25. kekkai/scanners/backends/native.py +240 -0
  26. kekkai/scanners/base.py +110 -0
  27. kekkai/scanners/container.py +144 -0
  28. kekkai/scanners/falco.py +237 -0
  29. kekkai/scanners/gitleaks.py +237 -0
  30. kekkai/scanners/semgrep.py +227 -0
  31. kekkai/scanners/trivy.py +246 -0
  32. kekkai/scanners/url_policy.py +163 -0
  33. kekkai/scanners/zap.py +340 -0
  34. kekkai/threatflow/__init__.py +94 -0
  35. kekkai/threatflow/artifacts.py +476 -0
  36. kekkai/threatflow/chunking.py +361 -0
  37. kekkai/threatflow/core.py +438 -0
  38. kekkai/threatflow/mermaid.py +374 -0
  39. kekkai/threatflow/model_adapter.py +491 -0
  40. kekkai/threatflow/prompts.py +277 -0
  41. kekkai/threatflow/redaction.py +228 -0
  42. kekkai/threatflow/sanitizer.py +643 -0
  43. kekkai/triage/__init__.py +33 -0
  44. kekkai/triage/app.py +168 -0
  45. kekkai/triage/audit.py +203 -0
  46. kekkai/triage/ignore.py +269 -0
  47. kekkai/triage/models.py +185 -0
  48. kekkai/triage/screens.py +341 -0
  49. kekkai/triage/widgets.py +169 -0
  50. kekkai_cli-1.0.0.dist-info/METADATA +135 -0
  51. kekkai_cli-1.0.0.dist-info/RECORD +90 -0
  52. kekkai_cli-1.0.0.dist-info/WHEEL +5 -0
  53. kekkai_cli-1.0.0.dist-info/entry_points.txt +3 -0
  54. kekkai_cli-1.0.0.dist-info/top_level.txt +3 -0
  55. kekkai_core/__init__.py +3 -0
  56. kekkai_core/ci/__init__.py +11 -0
  57. kekkai_core/ci/benchmarks.py +354 -0
  58. kekkai_core/ci/metadata.py +104 -0
  59. kekkai_core/ci/validators.py +92 -0
  60. kekkai_core/docker/__init__.py +17 -0
  61. kekkai_core/docker/metadata.py +153 -0
  62. kekkai_core/docker/sbom.py +173 -0
  63. kekkai_core/docker/security.py +158 -0
  64. kekkai_core/docker/signing.py +135 -0
  65. kekkai_core/redaction.py +84 -0
  66. kekkai_core/slsa/__init__.py +13 -0
  67. kekkai_core/slsa/verify.py +121 -0
  68. kekkai_core/windows/__init__.py +29 -0
  69. kekkai_core/windows/chocolatey.py +335 -0
  70. kekkai_core/windows/installer.py +256 -0
  71. kekkai_core/windows/scoop.py +165 -0
  72. kekkai_core/windows/validators.py +220 -0
  73. portal/__init__.py +19 -0
  74. portal/api.py +155 -0
  75. portal/auth.py +103 -0
  76. portal/enterprise/__init__.py +32 -0
  77. portal/enterprise/audit.py +435 -0
  78. portal/enterprise/licensing.py +342 -0
  79. portal/enterprise/rbac.py +276 -0
  80. portal/enterprise/saml.py +595 -0
  81. portal/ops/__init__.py +53 -0
  82. portal/ops/backup.py +553 -0
  83. portal/ops/log_shipper.py +469 -0
  84. portal/ops/monitoring.py +517 -0
  85. portal/ops/restore.py +469 -0
  86. portal/ops/secrets.py +408 -0
  87. portal/ops/upgrade.py +591 -0
  88. portal/tenants.py +340 -0
  89. portal/uploads.py +259 -0
  90. portal/web.py +384 -0
@@ -0,0 +1,374 @@
1
+ """Mermaid.js DFD generation for ThreatFlow.
2
+
3
+ Generates Mermaid.js syntax for Data Flow Diagrams (DFDs) with security encoding
4
+ to prevent XSS and injection attacks in rendered diagrams.
5
+
6
+ ASVS 5.0 Requirements:
7
+ - V5.3.3: Output encoding for target format
8
+ - V5.2.6: Validate structured output
9
+ - V5.5.2: Safe serialization (no executable content)
10
+
11
+ Threat Mitigations:
12
+ - XSS payloads in labels -> HTML escape + unsafe char replacement
13
+ - Mermaid syntax injection -> Strip special characters
14
+ - Architecture spoofing -> Validate against known entities
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import html
20
+ import re
21
+ from dataclasses import dataclass, field
22
+ from enum import Enum
23
+ from typing import TYPE_CHECKING
24
+
25
+ if TYPE_CHECKING:
26
+ from .artifacts import ThreatModelArtifacts
27
+
28
+ # Characters unsafe in Mermaid labels that could break syntax or enable injection
29
+ MERMAID_UNSAFE_CHARS = re.compile(r'[<>"\'`{}|\\;\[\]()]')
30
+
31
+ # Maximum label length to prevent DoS via extremely long labels
32
+ MAX_LABEL_LENGTH = 100
33
+
34
+
35
+ class NodeType(Enum):
36
+ """DFD node types with corresponding Mermaid shapes."""
37
+
38
+ EXTERNAL_ENTITY = "external_entity"
39
+ PROCESS = "process"
40
+ DATA_STORE = "data_store"
41
+
42
+
43
+ @dataclass(frozen=True)
44
+ class MermaidNode:
45
+ """A node in the Mermaid DFD.
46
+
47
+ Attributes:
48
+ id: Unique identifier for the node (alphanumeric + underscore only)
49
+ label: Display label (will be sanitized)
50
+ node_type: Type of DFD element
51
+ """
52
+
53
+ id: str
54
+ label: str
55
+ node_type: NodeType
56
+
57
+ def to_mermaid(self) -> str:
58
+ """Convert to Mermaid syntax with security encoding.
59
+
60
+ Returns:
61
+ Mermaid node definition string
62
+ """
63
+ safe_label = _encode_label(self.label)
64
+ safe_id = _sanitize_id(self.id)
65
+
66
+ # Map node types to Mermaid shapes
67
+ # External entities: parallelogram (trapezoid)
68
+ # Processes: circle (stadium shape)
69
+ # Data stores: cylinder
70
+ shapes = {
71
+ NodeType.EXTERNAL_ENTITY: f'{safe_id}[/"{safe_label}"/]',
72
+ NodeType.PROCESS: f'{safe_id}(["{safe_label}"])',
73
+ NodeType.DATA_STORE: f'{safe_id}[("{safe_label}")]',
74
+ }
75
+ return shapes.get(self.node_type, f'{safe_id}["{safe_label}"]')
76
+
77
+
78
+ @dataclass(frozen=True)
79
+ class MermaidEdge:
80
+ """An edge (data flow) in the Mermaid DFD.
81
+
82
+ Attributes:
83
+ source: Source node ID
84
+ target: Target node ID
85
+ label: Edge label describing the data flow
86
+ crosses_trust_boundary: Whether this flow crosses a trust boundary
87
+ """
88
+
89
+ source: str
90
+ target: str
91
+ label: str
92
+ crosses_trust_boundary: bool = False
93
+
94
+ def to_mermaid(self) -> str:
95
+ """Convert to Mermaid edge syntax with security encoding.
96
+
97
+ Returns:
98
+ Mermaid edge definition string
99
+ """
100
+ safe_source = _sanitize_id(self.source)
101
+ safe_target = _sanitize_id(self.target)
102
+ safe_label = _encode_label(self.label)
103
+
104
+ # Use thick arrow for trust boundary crossings
105
+ if self.crosses_trust_boundary:
106
+ return f'{safe_source} ==>|"{safe_label}"| {safe_target}'
107
+ return f'{safe_source} -->|"{safe_label}"| {safe_target}'
108
+
109
+
110
+ @dataclass
111
+ class MermaidDFDGenerator:
112
+ """Generates Mermaid.js Data Flow Diagrams.
113
+
114
+ Security-first design:
115
+ - All labels are HTML-encoded
116
+ - Special characters are replaced
117
+ - IDs are sanitized to alphanumeric only
118
+ """
119
+
120
+ title: str = "Data Flow Diagram"
121
+ direction: str = "TB" # TB (top-bottom), LR (left-right)
122
+ _nodes: list[MermaidNode] = field(default_factory=list)
123
+ _edges: list[MermaidEdge] = field(default_factory=list)
124
+ _trust_boundaries: list[tuple[str, list[str]]] = field(default_factory=list)
125
+
126
+ def add_node(self, node: MermaidNode) -> None:
127
+ """Add a node to the diagram."""
128
+ self._nodes.append(node)
129
+
130
+ def add_edge(self, edge: MermaidEdge) -> None:
131
+ """Add an edge to the diagram."""
132
+ self._edges.append(edge)
133
+
134
+ def add_trust_boundary(self, name: str, node_ids: list[str]) -> None:
135
+ """Add a trust boundary containing specified nodes."""
136
+ self._trust_boundaries.append((name, node_ids))
137
+
138
+ def generate(self) -> str:
139
+ """Generate complete Mermaid DFD syntax.
140
+
141
+ Returns:
142
+ Valid Mermaid flowchart syntax
143
+ """
144
+ lines: list[str] = []
145
+
146
+ # Header with title
147
+ safe_title = _encode_label(self.title)
148
+ lines.extend(
149
+ [
150
+ "---",
151
+ f"title: {safe_title}",
152
+ "---",
153
+ f"flowchart {self.direction}",
154
+ "",
155
+ ]
156
+ )
157
+
158
+ # Group nodes by type for organization
159
+ external = [n for n in self._nodes if n.node_type == NodeType.EXTERNAL_ENTITY]
160
+ processes = [n for n in self._nodes if n.node_type == NodeType.PROCESS]
161
+ stores = [n for n in self._nodes if n.node_type == NodeType.DATA_STORE]
162
+
163
+ # Add trust boundaries as subgraphs
164
+ nodes_in_boundaries: set[str] = set()
165
+ for boundary_name, node_ids in self._trust_boundaries:
166
+ safe_boundary = _sanitize_id(boundary_name)
167
+ safe_label = _encode_label(boundary_name)
168
+ lines.append(f' subgraph {safe_boundary}["{safe_label}"]')
169
+ for node_id in node_ids:
170
+ nodes_in_boundaries.add(node_id)
171
+ node = self._find_node(node_id)
172
+ if node:
173
+ lines.append(f" {node.to_mermaid()}")
174
+ lines.append(" end")
175
+ lines.append("")
176
+
177
+ # Add remaining nodes not in boundaries
178
+ if external:
179
+ lines.append(" %% External Entities")
180
+ for node in external:
181
+ if node.id not in nodes_in_boundaries:
182
+ lines.append(f" {node.to_mermaid()}")
183
+ lines.append("")
184
+
185
+ if processes:
186
+ lines.append(" %% Processes")
187
+ for node in processes:
188
+ if node.id not in nodes_in_boundaries:
189
+ lines.append(f" {node.to_mermaid()}")
190
+ lines.append("")
191
+
192
+ if stores:
193
+ lines.append(" %% Data Stores")
194
+ for node in stores:
195
+ if node.id not in nodes_in_boundaries:
196
+ lines.append(f" {node.to_mermaid()}")
197
+ lines.append("")
198
+
199
+ # Add edges
200
+ if self._edges:
201
+ lines.append(" %% Data Flows")
202
+ for edge in self._edges:
203
+ lines.append(f" {edge.to_mermaid()}")
204
+ lines.append("")
205
+
206
+ # Add styling for trust boundary crossings
207
+ boundary_edges = [e for e in self._edges if e.crosses_trust_boundary]
208
+ if boundary_edges:
209
+ lines.append(" %% Style trust boundary crossings")
210
+ lines.append(" linkStyle default stroke:#333,stroke-width:2px")
211
+
212
+ return "\n".join(lines)
213
+
214
+ def _find_node(self, node_id: str) -> MermaidNode | None:
215
+ """Find a node by ID."""
216
+ for node in self._nodes:
217
+ if node.id == node_id:
218
+ return node
219
+ return None
220
+
221
+ @classmethod
222
+ def from_artifacts(cls, artifacts: ThreatModelArtifacts) -> MermaidDFDGenerator:
223
+ """Create a Mermaid DFD generator from ThreatModelArtifacts.
224
+
225
+ Args:
226
+ artifacts: ThreatModelArtifacts containing DFD components
227
+
228
+ Returns:
229
+ Configured MermaidDFDGenerator
230
+ """
231
+ generator = cls(title=f"{artifacts.repo_name} Data Flow Diagram")
232
+
233
+ # Track node IDs for edge validation
234
+ node_ids: set[str] = set()
235
+
236
+ # Add external entities
237
+ for i, entity in enumerate(artifacts.external_entities):
238
+ node_id = f"ext_{i}"
239
+ generator.add_node(
240
+ MermaidNode(
241
+ id=node_id,
242
+ label=entity,
243
+ node_type=NodeType.EXTERNAL_ENTITY,
244
+ )
245
+ )
246
+ node_ids.add(node_id)
247
+
248
+ # Add processes
249
+ for i, process in enumerate(artifacts.processes):
250
+ node_id = f"proc_{i}"
251
+ generator.add_node(
252
+ MermaidNode(
253
+ id=node_id,
254
+ label=process,
255
+ node_type=NodeType.PROCESS,
256
+ )
257
+ )
258
+ node_ids.add(node_id)
259
+
260
+ # Add data stores
261
+ for i, store in enumerate(artifacts.data_stores):
262
+ node_id = f"store_{i}"
263
+ generator.add_node(
264
+ MermaidNode(
265
+ id=node_id,
266
+ label=store,
267
+ node_type=NodeType.DATA_STORE,
268
+ )
269
+ )
270
+ node_ids.add(node_id)
271
+
272
+ # Build lookup for node names to IDs
273
+ name_to_id = _build_name_to_id_map(generator._nodes)
274
+
275
+ # Add data flows as edges
276
+ for flow in artifacts.dataflows:
277
+ source_id = name_to_id.get(flow.source.lower(), _sanitize_id(flow.source))
278
+ target_id = name_to_id.get(flow.destination.lower(), _sanitize_id(flow.destination))
279
+ generator.add_edge(
280
+ MermaidEdge(
281
+ source=source_id,
282
+ target=target_id,
283
+ label=flow.data_type,
284
+ crosses_trust_boundary=flow.trust_boundary_crossed,
285
+ )
286
+ )
287
+
288
+ # Add trust boundaries
289
+ if artifacts.trust_boundaries:
290
+ # Group internal processes
291
+ internal_ids = [f"proc_{i}" for i in range(len(artifacts.processes))]
292
+ if internal_ids:
293
+ generator.add_trust_boundary("Internal_Network", internal_ids)
294
+
295
+ return generator
296
+
297
+
298
+ def _encode_label(label: str) -> str:
299
+ """Encode a label for safe use in Mermaid diagrams.
300
+
301
+ Applies:
302
+ 1. HTML escaping for XSS prevention
303
+ 2. Replacement of Mermaid-unsafe characters
304
+ 3. Length truncation
305
+
306
+ Args:
307
+ label: Raw label text
308
+
309
+ Returns:
310
+ Sanitized label safe for Mermaid
311
+ """
312
+ # HTML escape first
313
+ safe = html.escape(label, quote=True)
314
+
315
+ # Replace unsafe Mermaid characters with underscores
316
+ safe = MERMAID_UNSAFE_CHARS.sub("_", safe)
317
+
318
+ # Truncate to prevent DoS
319
+ if len(safe) > MAX_LABEL_LENGTH:
320
+ safe = safe[: MAX_LABEL_LENGTH - 3] + "..."
321
+
322
+ return safe
323
+
324
+
325
+ def _sanitize_id(id_str: str) -> str:
326
+ """Sanitize a node/subgraph ID for Mermaid.
327
+
328
+ IDs must be alphanumeric with underscores only.
329
+
330
+ Args:
331
+ id_str: Raw ID string
332
+
333
+ Returns:
334
+ Sanitized ID safe for Mermaid
335
+ """
336
+ # Replace spaces and special chars with underscores
337
+ safe = re.sub(r"[^a-zA-Z0-9_]", "_", id_str)
338
+
339
+ # Ensure starts with letter (Mermaid requirement)
340
+ if safe and not safe[0].isalpha():
341
+ safe = "n_" + safe
342
+
343
+ # Ensure not empty
344
+ if not safe:
345
+ safe = "node"
346
+
347
+ return safe
348
+
349
+
350
+ def _build_name_to_id_map(nodes: list[MermaidNode]) -> dict[str, str]:
351
+ """Build a mapping from node labels (lowercase) to IDs.
352
+
353
+ Args:
354
+ nodes: List of MermaidNodes
355
+
356
+ Returns:
357
+ Dictionary mapping lowercase labels to node IDs
358
+ """
359
+ return {node.label.lower(): node.id for node in nodes}
360
+
361
+
362
+ def generate_dfd_mermaid(artifacts: ThreatModelArtifacts) -> str:
363
+ """Generate Mermaid DFD syntax from ThreatModelArtifacts.
364
+
365
+ Convenience function for simple usage.
366
+
367
+ Args:
368
+ artifacts: ThreatModelArtifacts containing DFD components
369
+
370
+ Returns:
371
+ Mermaid flowchart syntax string
372
+ """
373
+ generator = MermaidDFDGenerator.from_artifacts(artifacts)
374
+ return generator.generate()