exaai-agent 2.0.9__py3-none-any.whl → 2.1.2__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.
@@ -49,6 +49,19 @@ from .vuln_validator import (
49
49
  VulnerabilityReport,
50
50
  create_vuln_report,
51
51
  )
52
+ from .prompt_injection import (
53
+ PromptInjectionScanner,
54
+ scan_for_prompt_injection,
55
+ generate_injection_payloads,
56
+ analyze_llm_response,
57
+ detect_jailbreak_success,
58
+ )
59
+ from .k8s_scanner import (
60
+ K8sScanner,
61
+ scan_cluster,
62
+ check_rbac,
63
+ check_pod_security,
64
+ )
52
65
  from .tool_prompts import (
53
66
  get_fuzzer_prompt,
54
67
  get_analyzer_prompt,
@@ -127,6 +140,17 @@ __all__ = [
127
140
  "Severity",
128
141
  "VulnerabilityReport",
129
142
  "create_vuln_report",
143
+ # Prompt Injection Scanner
144
+ "PromptInjectionScanner",
145
+ "scan_for_prompt_injection",
146
+ "generate_injection_payloads",
147
+ "analyze_llm_response",
148
+ "detect_jailbreak_success",
149
+ # K8s Scanner
150
+ "K8sScanner",
151
+ "scan_cluster",
152
+ "check_rbac",
153
+ "check_pod_security",
130
154
  # Tool Prompts
131
155
  "get_fuzzer_prompt",
132
156
  "get_analyzer_prompt",
@@ -65,7 +65,7 @@ async def _execute_tool_in_sandbox(tool_name: str, agent_state: Any, **kwargs: A
65
65
  async with httpx.AsyncClient(trust_env=False) as client:
66
66
  try:
67
67
  response = await client.post(
68
- request_url, json=request_data, headers=headers, timeout=None
68
+ request_url, json=request_data, headers=headers, timeout=600.0 # 10 min max
69
69
  )
70
70
  response.raise_for_status()
71
71
  response_data = response.json()
@@ -0,0 +1,29 @@
1
+ """
2
+ Kubernetes Security Scanner Module.
3
+
4
+ Provides comprehensive security auditing for Kubernetes clusters:
5
+ - RBAC analysis (excessive permissions)
6
+ - Pod Security Standards (PSS) compliance
7
+ - Network Policy auditing
8
+ - Secret management checks
9
+ """
10
+
11
+ from .k8s_actions import (
12
+ K8sScanner,
13
+ scan_cluster,
14
+ check_rbac,
15
+ check_pod_security,
16
+ CheckStatus,
17
+ Severity,
18
+ SecurityFinding,
19
+ )
20
+
21
+ __all__ = [
22
+ "K8sScanner",
23
+ "scan_cluster",
24
+ "check_rbac",
25
+ "check_pod_security",
26
+ "CheckStatus",
27
+ "Severity",
28
+ "SecurityFinding",
29
+ ]
@@ -0,0 +1,313 @@
1
+ """
2
+ Kubernetes Security Scanner - Core Actions
3
+
4
+ Comprehensive security testing for Kubernetes clusters including:
5
+ - RBAC analysis (excessive permissions)
6
+ - Pod Security Standards (PSS) compliance
7
+ - Network Policy auditing
8
+ - Secret management checks
9
+ - API Server configuration review
10
+ - Container vulnerability scanning integration
11
+
12
+ Author: ALhilali
13
+ Version: 1.0.0
14
+ """
15
+
16
+ import logging
17
+ import json
18
+ import subprocess
19
+ from dataclasses import dataclass, field
20
+ from enum import Enum
21
+ from typing import List, Dict, Any, Optional
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ class CheckStatus(Enum):
27
+ """Status of a security check."""
28
+ PASS = "PASS"
29
+ FAIL = "FAIL"
30
+ WARN = "WARN"
31
+ ERROR = "ERROR"
32
+ SKIP = "SKIP"
33
+
34
+
35
+ class Severity(Enum):
36
+ """Severity of a finding."""
37
+ CRITICAL = "CRITICAL"
38
+ HIGH = "HIGH"
39
+ MEDIUM = "MEDIUM"
40
+ LOW = "LOW"
41
+ INFO = "INFO"
42
+
43
+
44
+ @dataclass
45
+ class SecurityFinding:
46
+ """Represents a security finding in the cluster."""
47
+ check_id: str
48
+ title: str
49
+ description: str
50
+ severity: Severity
51
+ resource_kind: str
52
+ resource_name: str
53
+ namespace: str
54
+ remediation: str
55
+ status: CheckStatus = CheckStatus.FAIL
56
+
57
+
58
+ class K8sScanner:
59
+ """
60
+ Kubernetes Security Scanner.
61
+ Uses kubectl and specialized logic to audit cluster security.
62
+ """
63
+
64
+ def __init__(self, context: Optional[str] = None, verbose: bool = False):
65
+ self.context = context
66
+ self.verbose = verbose
67
+ self.findings: List[SecurityFinding] = []
68
+ self._check_kubectl_availability()
69
+
70
+ def _check_kubectl_availability(self):
71
+ """Ensure kubectl is installed and accessible."""
72
+ try:
73
+ subprocess.run(["kubectl", "version", "--client"], capture_output=True, check=True)
74
+ except (subprocess.CalledProcessError, FileNotFoundError):
75
+ logger.warning("kubectl not found. Some checks may fail.")
76
+
77
+ def _run_kubectl(self, args: List[str]) -> Dict[str, Any]:
78
+ """Run a kubectl command and return JSON output."""
79
+ cmd = ["kubectl"] + args + ["-o", "json"]
80
+ if self.context:
81
+ cmd.extend(["--context", self.context])
82
+
83
+ try:
84
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
85
+ return json.loads(result.stdout)
86
+ except subprocess.CalledProcessError as e:
87
+ logger.error(f"kubectl command failed: {e.stderr}")
88
+ return {}
89
+ except json.JSONDecodeError:
90
+ logger.error("Failed to decode kubectl output")
91
+ return {}
92
+
93
+ def scan(self, namespaces: Optional[List[str]] = None) -> List[SecurityFinding]:
94
+ """Run all enabled security checks."""
95
+ target_ns = namespaces or self._get_all_namespaces()
96
+
97
+ logger.info(f"Scanning namespaces: {target_ns}")
98
+
99
+ # Cluster-wide checks (run once)
100
+ self._check_cluster_rbac()
101
+
102
+ # Namespace-scoped checks
103
+ for ns in target_ns:
104
+ self._check_rbac(ns)
105
+ self._check_pod_security(ns)
106
+ self._check_network_policies(ns)
107
+ self._check_secrets(ns)
108
+
109
+ return self.findings
110
+
111
+ def _check_cluster_rbac(self):
112
+ """Check cluster-wide RBAC configurations."""
113
+ # Check ClusterRoleBindings for cluster-admin
114
+ bindings = self._run_kubectl(["get", "clusterrolebindings"])
115
+ for binding in bindings.get("items", []):
116
+ role_name = binding.get("roleRef", {}).get("name", "")
117
+ binding_name = binding["metadata"]["name"]
118
+
119
+ # Skip system bindings
120
+ if binding_name.startswith("system:"):
121
+ continue
122
+
123
+ if role_name == "cluster-admin":
124
+ for subject in binding.get("subjects", []):
125
+ if subject.get("kind") == "ServiceAccount":
126
+ self.findings.append(SecurityFinding(
127
+ check_id="RBAC-002",
128
+ title="ServiceAccount with cluster-admin",
129
+ description=f"ServiceAccount '{subject.get('name')}' in namespace '{subject.get('namespace', 'default')}' has cluster-admin binding via '{binding_name}'.",
130
+ severity=Severity.CRITICAL,
131
+ resource_kind="ClusterRoleBinding",
132
+ resource_name=binding_name,
133
+ namespace=subject.get("namespace", "cluster-wide"),
134
+ remediation="Remove cluster-admin binding. Create a scoped Role with minimum required permissions."
135
+ ))
136
+ elif subject.get("kind") == "User" and not subject.get("name", "").startswith("system:"):
137
+ self.findings.append(SecurityFinding(
138
+ check_id="RBAC-003",
139
+ title="User with cluster-admin",
140
+ description=f"User '{subject.get('name')}' has cluster-admin binding via '{binding_name}'.",
141
+ severity=Severity.HIGH,
142
+ resource_kind="ClusterRoleBinding",
143
+ resource_name=binding_name,
144
+ namespace="cluster-wide",
145
+ remediation="Review if user truly needs cluster-admin. Apply least privilege principle."
146
+ ))
147
+
148
+ def _get_all_namespaces(self) -> List[str]:
149
+ """Get list of all namespaces."""
150
+ data = self._run_kubectl(["get", "namespaces"])
151
+ return [item["metadata"]["name"] for item in data.get("items", [])]
152
+
153
+ def _check_rbac(self, namespace: str):
154
+ """Check for risky RBAC configurations."""
155
+ # Check 1: Roles with '*' verb
156
+ roles = self._run_kubectl(["get", "roles", "-n", namespace])
157
+ for role in roles.get("items", []):
158
+ name = role["metadata"]["name"]
159
+ for rule in role.get("rules", []):
160
+ if "*" in rule.get("verbs", []) and "*" in rule.get("resources", []):
161
+ self.findings.append(SecurityFinding(
162
+ check_id="RBAC-001",
163
+ title="Cluster Admin-like Role",
164
+ description=f"Role '{name}' has wildcard permissions (*/*).",
165
+ severity=Severity.CRITICAL,
166
+ resource_kind="Role",
167
+ resource_name=name,
168
+ namespace=namespace,
169
+ remediation="Apply least privilege. Remove wildcard permissions."
170
+ ))
171
+
172
+ # Check 2: ServiceAccounts with cluster-admin binding
173
+ # (Simplified check - full graph analysis requires more logic)
174
+
175
+ def _check_pod_security(self, namespace: str):
176
+ """Check pods against Pod Security Standards (PSS)."""
177
+ pods = self._run_kubectl(["get", "pods", "-n", namespace])
178
+ for pod in pods.get("items", []):
179
+ name = pod["metadata"]["name"]
180
+ spec = pod.get("spec", {})
181
+
182
+ # Check 1: Privileged containers
183
+ for container in spec.get("containers", []):
184
+ security_context = container.get("securityContext", {})
185
+ if security_context.get("privileged", False):
186
+ self.findings.append(SecurityFinding(
187
+ check_id="PSS-001",
188
+ title="Privileged Container",
189
+ description=f"Container '{container['name']}' in pod '{name}' runs as privileged.",
190
+ severity=Severity.HIGH,
191
+ resource_kind="Pod",
192
+ resource_name=name,
193
+ namespace=namespace,
194
+ remediation="Remove 'privileged: true' from securityContext unless absolutely necessary."
195
+ ))
196
+
197
+ # Check 2: Host PID/Network/IPC
198
+ if spec.get("hostPID") or spec.get("hostNetwork") or spec.get("hostIPC"):
199
+ self.findings.append(SecurityFinding(
200
+ check_id="PSS-002",
201
+ title="Host Namespace Usage",
202
+ description=f"Pod '{name}' shares host namespaces (PID/Net/IPC).",
203
+ severity=Severity.HIGH,
204
+ resource_kind="Pod",
205
+ resource_name=name,
206
+ namespace=namespace,
207
+ remediation="Disable hostPID, hostNetwork, and hostIPC."
208
+ ))
209
+
210
+ def _check_network_policies(self, namespace: str):
211
+ """Check if network policies are defined."""
212
+ policies = self._run_kubectl(["get", "networkpolicies", "-n", namespace])
213
+ if not policies.get("items"):
214
+ self.findings.append(SecurityFinding(
215
+ check_id="NET-001",
216
+ title="Missing Network Policy",
217
+ description=f"Namespace '{namespace}' has no NetworkPolicies defined. Traffic is unrestricted.",
218
+ severity=Severity.MEDIUM,
219
+ resource_kind="Namespace",
220
+ resource_name=namespace,
221
+ namespace=namespace,
222
+ remediation="Define a default deny-all NetworkPolicy."
223
+ ))
224
+
225
+ def _check_secrets(self, namespace: str):
226
+ """Check for secrets issues."""
227
+ pods = self._run_kubectl(["get", "pods", "-n", namespace])
228
+ for pod in pods.get("items", []):
229
+ name = pod["metadata"]["name"]
230
+ spec = pod.get("spec", {})
231
+
232
+ for container in spec.get("containers", []):
233
+ container_name = container.get("name", "unknown")
234
+
235
+ # Check for secrets in plain env vars (bad practice)
236
+ for env in container.get("env", []):
237
+ env_name = env.get("name", "").lower()
238
+ env_value = env.get("value", "")
239
+
240
+ # Skip if using secretKeyRef (proper method)
241
+ if env.get("valueFrom", {}).get("secretKeyRef"):
242
+ continue
243
+
244
+ # Check for suspicious env var names with plain values
245
+ sensitive_keywords = ["password", "secret", "key", "token",
246
+ "api_key", "apikey", "auth", "credential",
247
+ "private", "access_key", "secret_key"]
248
+
249
+ if any(kw in env_name for kw in sensitive_keywords) and env_value:
250
+ self.findings.append(SecurityFinding(
251
+ check_id="SEC-001",
252
+ title="Secret in Plain Environment Variable",
253
+ description=f"Container '{container_name}' in pod '{name}' has sensitive data in plain env var '{env.get('name')}'.",
254
+ severity=Severity.HIGH,
255
+ resource_kind="Pod",
256
+ resource_name=name,
257
+ namespace=namespace,
258
+ remediation="Use Kubernetes Secrets with secretKeyRef instead of plain values. Never commit secrets in manifests."
259
+ ))
260
+
261
+ # Check for automountServiceAccountToken
262
+ if spec.get("automountServiceAccountToken", True):
263
+ # Only flag if not system namespace
264
+ if namespace not in ["kube-system", "kube-public", "kube-node-lease"]:
265
+ self.findings.append(SecurityFinding(
266
+ check_id="SEC-002",
267
+ title="ServiceAccount Token Auto-Mounted",
268
+ description=f"Pod '{name}' has automountServiceAccountToken enabled (default).",
269
+ severity=Severity.LOW,
270
+ resource_kind="Pod",
271
+ resource_name=name,
272
+ namespace=namespace,
273
+ remediation="Set automountServiceAccountToken: false unless the pod needs K8s API access."
274
+ ))
275
+
276
+ def export_report(self) -> Dict[str, Any]:
277
+ """Export findings summary."""
278
+ return {
279
+ "total_findings": len(self.findings),
280
+ "findings": [
281
+ {
282
+ "id": f.check_id,
283
+ "title": f.title,
284
+ "severity": f.severity.value,
285
+ "resource": f"{f.namespace}/{f.resource_kind}/{f.resource_name}",
286
+ "remediation": f.remediation
287
+ }
288
+ for f in self.findings
289
+ ]
290
+ }
291
+
292
+
293
+ # === Convenience Functions ===
294
+
295
+ def scan_cluster(context: Optional[str] = None) -> Dict[str, Any]:
296
+ """Run a full cluster scan."""
297
+ scanner = K8sScanner(context=context)
298
+ scanner.scan()
299
+ return scanner.export_report()
300
+
301
+
302
+ def check_rbac(namespace: str = "default") -> List[Dict[str, Any]]:
303
+ """Run RBAC checks only."""
304
+ scanner = K8sScanner()
305
+ scanner._check_rbac(namespace)
306
+ return scanner.export_report()["findings"]
307
+
308
+
309
+ def check_pod_security(namespace: str = "default") -> List[Dict[str, Any]]:
310
+ """Run Pod Security checks only."""
311
+ scanner = K8sScanner()
312
+ scanner._check_pod_security(namespace)
313
+ return scanner.export_report()["findings"]
@@ -0,0 +1,26 @@
1
+ """
2
+ AI Prompt Injection Scanner - Detect LLM vulnerabilities in AI applications.
3
+
4
+ This module provides tools for testing AI/LLM applications for:
5
+ - Direct prompt injection
6
+ - Indirect prompt injection
7
+ - Jailbreak attempts
8
+ - Data exfiltration via prompts
9
+ - System prompt leakage
10
+ """
11
+
12
+ from .prompt_injection_actions import (
13
+ PromptInjectionScanner,
14
+ scan_for_prompt_injection,
15
+ generate_injection_payloads,
16
+ analyze_llm_response,
17
+ detect_jailbreak_success,
18
+ )
19
+
20
+ __all__ = [
21
+ "PromptInjectionScanner",
22
+ "scan_for_prompt_injection",
23
+ "generate_injection_payloads",
24
+ "analyze_llm_response",
25
+ "detect_jailbreak_success",
26
+ ]