secureai-sdk 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.
secureai/__init__.py ADDED
@@ -0,0 +1,48 @@
1
+ """
2
+ SecureAI Python SDK — Enterprise AI Security, Governance & Guardrails
3
+ Powered by AcadmyAI (https://secure.acadmyai.com)
4
+ """
5
+
6
+ from .guard import (
7
+ guard,
8
+ inspect_input,
9
+ inspect_output,
10
+ SecurityPolicy,
11
+ SecurityViolationError
12
+ )
13
+ from .client import (
14
+ SecureAI,
15
+ AsyncSecureAI,
16
+ SecureAIClient
17
+ )
18
+ from .vault import (
19
+ PIIVault,
20
+ VaultedString,
21
+ vault_tokenize,
22
+ vault_detokenize,
23
+ default_vault
24
+ )
25
+ from .mcp import MCPToolGuard
26
+ from .integrations import wrap_openai
27
+
28
+ __version__ = "1.0.0"
29
+ __author__ = "AcadmyAI"
30
+ __license__ = "Apache 2.0"
31
+
32
+ __all__ = [
33
+ "guard",
34
+ "inspect_input",
35
+ "inspect_output",
36
+ "SecurityPolicy",
37
+ "SecurityViolationError",
38
+ "SecureAI",
39
+ "AsyncSecureAI",
40
+ "SecureAIClient",
41
+ "PIIVault",
42
+ "VaultedString",
43
+ "vault_tokenize",
44
+ "vault_detokenize",
45
+ "default_vault",
46
+ "MCPToolGuard",
47
+ "wrap_openai",
48
+ ]
secureai/cli.py ADDED
@@ -0,0 +1,92 @@
1
+ import sys
2
+ import os
3
+ import argparse
4
+ import time
5
+ from .guard import inspect_input, FAST_INJECTION_PATTERNS
6
+ from .vault import default_vault
7
+
8
+ def main():
9
+ parser = argparse.ArgumentParser(
10
+ prog="secureai",
11
+ description="SecureAI Enterprise Security, Guardrails & Shadow AI Scanner CLI — Powered by AcadmyAI"
12
+ )
13
+ subparsers = parser.add_subparsers(dest="command", help="Available subcommands")
14
+
15
+ # Command: scan
16
+ scan_parser = subparsers.add_parser("scan", help="Scan a text prompt for prompt injection, jailbreaks, and PII")
17
+ scan_parser.add_argument("prompt", type=str, help="Prompt text to inspect")
18
+
19
+ # Command: vault
20
+ vault_parser = subparsers.add_parser("vault", help="Tokenize sensitive PII entities in a string")
21
+ vault_parser.add_argument("text", type=str, help="Text to tokenize")
22
+
23
+ # Command: audit
24
+ audit_parser = subparsers.add_parser("audit", help="Audit local codebase for unapproved Shadow AI API calls")
25
+ audit_parser.add_argument("directory", type=str, default=".", nargs="?", help="Directory path to scan")
26
+
27
+ # Command: version
28
+ subparsers.add_parser("version", help="Show SecureAI SDK version")
29
+
30
+ args = parser.parse_args()
31
+
32
+ if args.command == "scan":
33
+ print(f"\nšŸ›”ļø SecureAI Heuristic Fast-Path Scanner")
34
+ print("=" * 50)
35
+ res = inspect_input(args.prompt)
36
+ vaulted = default_vault.tokenize(args.prompt)
37
+
38
+ status = "PASSED" if res["is_safe"] else "BLOCKED"
39
+ color = "\033[92m" if res["is_safe"] else "\033[91m"
40
+ reset = "\033[0m"
41
+
42
+ print(f"Status: {color}{status}{reset}")
43
+ print(f"Risk Score: {res['risk_score']} / 1.0")
44
+ print(f"Threat Detected: {res['threat_detected'] or 'None'}")
45
+ print(f"PII Entities: {vaulted.redacted_count} tokenized")
46
+ print(f"Sanitized Text: {vaulted}")
47
+ print(f"Latency: {res['latency_ms']} ms\n")
48
+ sys.exit(0 if res["is_safe"] else 1)
49
+
50
+ elif args.command == "vault":
51
+ vaulted = default_vault.tokenize(args.text)
52
+ print(f"\nšŸ” Vaulted Output ({vaulted.redacted_count} entities redacted):")
53
+ print(str(vaulted))
54
+ print(f"\nToken Map: {vaulted.token_map}\n")
55
+
56
+ elif args.command == "audit":
57
+ dir_to_scan = os.path.abspath(args.directory)
58
+ print(f"\nšŸ” Scanning repository '{dir_to_scan}' for Shadow AI endpoints...")
59
+
60
+ ai_patterns = ["api.openai.com", "api.anthropic.com", "generativelanguage.googleapis.com", "groq.com", "together.ai"]
61
+ found_files = []
62
+
63
+ for root, _, files in os.walk(dir_to_scan):
64
+ for file in files:
65
+ if file.endswith((".py", ".js", ".ts", ".go", ".java")):
66
+ filepath = os.path.join(root, file)
67
+ try:
68
+ with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
69
+ content = f.read()
70
+ for pat in ai_patterns:
71
+ if pat in content:
72
+ found_files.append((filepath, pat))
73
+ except Exception:
74
+ pass
75
+
76
+ if found_files:
77
+ print(f"āš ļø Found {len(found_files)} potential unmanaged AI direct API connections:")
78
+ for fp, pat in found_files[:10]:
79
+ print(f" • {fp} -> {pat}")
80
+ print("\nšŸ’” Tip: Route requests through SecureAI Gateway for compliance and DLP protection.")
81
+ else:
82
+ print("āœ… No unmanaged direct AI endpoints detected.")
83
+
84
+ elif args.command == "version":
85
+ from . import __version__
86
+ print(f"SecureAI Python SDK v{__version__} — https://secure.acadmyai.com")
87
+
88
+ else:
89
+ parser.print_help()
90
+
91
+ if __name__ == "__main__":
92
+ main()
secureai/client.py ADDED
@@ -0,0 +1,177 @@
1
+ import httpx
2
+ from typing import Dict, Any, Optional, List, Union
3
+
4
+ DEFAULT_GATEWAY_URL = "https://secure.acadmyai.com/v1"
5
+
6
+ class SecureAI:
7
+ """Synchronous Enterprise Client for SecureAI Cloud & VPC Gateways."""
8
+
9
+ def __init__(
10
+ self,
11
+ api_key: Optional[str] = None,
12
+ base_url: str = DEFAULT_GATEWAY_URL,
13
+ timeout: float = 10.0
14
+ ):
15
+ self.api_key = api_key or "sec_guest_token"
16
+ self.base_url = base_url.rstrip("/")
17
+ self._client = httpx.Client(
18
+ base_url=self.base_url,
19
+ headers={
20
+ "Authorization": f"Bearer {self.api_key}",
21
+ "Content-Type": "application/json",
22
+ "User-Agent": "SecureAI-Python-SDK/1.0.0"
23
+ },
24
+ timeout=timeout
25
+ )
26
+
27
+ def inspect(
28
+ self,
29
+ prompt: str,
30
+ user_id: str = "default_user",
31
+ role: str = "developer",
32
+ department: str = "engineering",
33
+ clearance_level: int = 1
34
+ ) -> Dict[str, Any]:
35
+ """Performs real-time cloud gateway inspection across Injection, Toxicity, and PII Vault."""
36
+ resp = self._client.post("/guard/inspect", json={
37
+ "prompt": prompt,
38
+ "user_id": user_id,
39
+ "role": role,
40
+ "department": department,
41
+ "clearance_level": clearance_level
42
+ })
43
+ resp.raise_for_status()
44
+ return resp.json()
45
+
46
+ def tokenize_pii(self, text: str) -> Dict[str, Any]:
47
+ """Calls Cloud Vault to tokenize sensitive entities."""
48
+ resp = self._client.post("/vault/tokenize", json={"text": text})
49
+ resp.raise_for_status()
50
+ return resp.json()
51
+
52
+ def detokenize_pii(self, text: str, token_map: Dict[str, str]) -> Dict[str, Any]:
53
+ """Calls Cloud Vault to detokenize surrogate tokens."""
54
+ resp = self._client.post("/vault/detokenize", json={"text": text, "token_map": token_map})
55
+ resp.raise_for_status()
56
+ return resp.json()
57
+
58
+ def authorize_mcp_tool(
59
+ self,
60
+ tool_name: str,
61
+ params: Dict[str, Any],
62
+ clearance_level: int = 1,
63
+ session_id: str = "agent_session_1"
64
+ ) -> Dict[str, Any]:
65
+ """Evaluates Model Context Protocol (MCP) tool execution for SQLi and destructive side-effects."""
66
+ resp = self._client.post("/mcp/proxy", json={
67
+ "method": "tools/call",
68
+ "params": {"name": tool_name, "arguments": params},
69
+ "session_id": session_id
70
+ })
71
+ resp.raise_for_status()
72
+ return resp.json()
73
+
74
+ def scan_shadow_ai(self, code_samples: List[str]) -> Dict[str, Any]:
75
+ """Scans codebases or repository files for unapproved LLM/AI model calls."""
76
+ resp = self._client.post("/shadow-ai/scan", json={"code_samples": code_samples})
77
+ resp.raise_for_status()
78
+ return resp.json()
79
+
80
+ def close(self):
81
+ """Closes internal HTTP transport."""
82
+ self._client.close()
83
+
84
+ def __enter__(self):
85
+ return self
86
+
87
+ def __exit__(self, exc_type, exc_val, exc_tb):
88
+ self.close()
89
+
90
+
91
+ class AsyncSecureAI:
92
+ """Asynchronous Enterprise Client for SecureAI Cloud & VPC Gateways."""
93
+
94
+ def __init__(
95
+ self,
96
+ api_key: Optional[str] = None,
97
+ base_url: str = DEFAULT_GATEWAY_URL,
98
+ timeout: float = 10.0
99
+ ):
100
+ self.api_key = api_key or "sec_guest_token"
101
+ self.base_url = base_url.rstrip("/")
102
+ self._client = httpx.AsyncClient(
103
+ base_url=self.base_url,
104
+ headers={
105
+ "Authorization": f"Bearer {self.api_key}",
106
+ "Content-Type": "application/json",
107
+ "User-Agent": "SecureAI-Python-SDK/1.0.0"
108
+ },
109
+ timeout=timeout
110
+ )
111
+
112
+ async def inspect(
113
+ self,
114
+ prompt: str,
115
+ user_id: str = "default_user",
116
+ role: str = "developer",
117
+ department: str = "engineering",
118
+ clearance_level: int = 1
119
+ ) -> Dict[str, Any]:
120
+ """Asynchronously calls SecureAI Gateway inspection endpoint."""
121
+ resp = await self._client.post("/guard/inspect", json={
122
+ "prompt": prompt,
123
+ "user_id": user_id,
124
+ "role": role,
125
+ "department": department,
126
+ "clearance_level": clearance_level
127
+ })
128
+ resp.raise_for_status()
129
+ return resp.json()
130
+
131
+ async def tokenize_pii(self, text: str) -> Dict[str, Any]:
132
+ """Asynchronously tokenizes PII entities via Cloud Vault."""
133
+ resp = await self._client.post("/vault/tokenize", json={"text": text})
134
+ resp.raise_for_status()
135
+ return resp.json()
136
+
137
+ async def detokenize_pii(self, text: str, token_map: Dict[str, str]) -> Dict[str, Any]:
138
+ """Asynchronously detokenizes surrogate tokens."""
139
+ resp = await self._client.post("/vault/detokenize", json={"text": text, "token_map": token_map})
140
+ resp.raise_for_status()
141
+ return resp.json()
142
+
143
+ async def authorize_mcp_tool(
144
+ self,
145
+ tool_name: str,
146
+ params: Dict[str, Any],
147
+ clearance_level: int = 1,
148
+ session_id: str = "agent_session_1"
149
+ ) -> Dict[str, Any]:
150
+ """Asynchronously evaluates Model Context Protocol tool execution."""
151
+ resp = await self._client.post("/mcp/proxy", json={
152
+ "method": "tools/call",
153
+ "params": {"name": tool_name, "arguments": params},
154
+ "session_id": session_id
155
+ })
156
+ resp.raise_for_status()
157
+ return resp.json()
158
+
159
+ async def scan_shadow_ai(self, code_samples: List[str]) -> Dict[str, Any]:
160
+ """Asynchronously scans code samples for Shadow AI endpoints."""
161
+ resp = await self._client.post("/shadow-ai/scan", json={"code_samples": code_samples})
162
+ resp.raise_for_status()
163
+ return resp.json()
164
+
165
+ async def aclose(self):
166
+ """Closes async HTTP transport."""
167
+ await self._client.aclose()
168
+
169
+ async def __aenter__(self):
170
+ return self
171
+
172
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
173
+ await self.aclose()
174
+
175
+
176
+ # Backward compatibility alias
177
+ SecureAIClient = SecureAI
secureai/guard.py ADDED
@@ -0,0 +1,224 @@
1
+ import functools
2
+ import inspect
3
+ import re
4
+ import time
5
+ import asyncio
6
+ from typing import Callable, Any, Dict, Optional, List, Union, Tuple
7
+ from .vault import default_vault, VaultedString
8
+
9
+ class SecurityViolationError(Exception):
10
+ """Raised when an input prompt, parameter, or model output violates the security policy."""
11
+ def __init__(
12
+ self,
13
+ message: str,
14
+ risk_score: float = 0.95,
15
+ threat_type: str = "SECURITY_VIOLATION",
16
+ matched_patterns: Optional[List[str]] = None,
17
+ details: Optional[Dict[str, Any]] = None
18
+ ):
19
+ super().__init__(message)
20
+ self.risk_score = risk_score
21
+ self.threat_type = threat_type
22
+ self.matched_patterns = matched_patterns or []
23
+ self.details = details or {}
24
+
25
+ # Fast-path compiled heuristic matrices (<0.2ms evaluation)
26
+ FAST_INJECTION_PATTERNS = [
27
+ # Direct overrides and ignore commands (singular, plural, stem variations)
28
+ (re.compile(r"(?i)\b(?:ignore|disregard|forget|skip|drop|override|bypass|disable|negate|cancel|erase)\s+(?:all\s+)?(?:(?:previous|prior|above|former|initial|system)\s+)?(?:instructions?|prompts?|rules?|commands?|guidelines?|context|constraints?|directives?|filters?|guardrails?)"), "DIRECT_OVERRIDE", 0.96),
29
+ (re.compile(r"(?i)\bignore\s+(?:all\s+)?(?:the\s+)?(?:previous|prior|above|safety|system)"), "DIRECT_OVERRIDE", 0.95),
30
+ (re.compile(r"(?i)\bnew\s+rule:\s*(?:ignore|disregard|forget|override)"), "DIRECT_OVERRIDE", 0.92),
31
+
32
+ # Jailbreak roleplays
33
+ (re.compile(r"(?i)\byou\s+are\s+now\s+(?:DAN|unfiltered|jailbroken|unrestricted|free\s+of\s+rules|an\s+adversary|in\s+developer\s+mode|in\s+god\s+mode)"), "JAILBREAK_ROLEPLAY", 0.94),
34
+ (re.compile(r"(?i)\bdo\s+anything\s+now\b"), "JAILBREAK_DAN", 0.96),
35
+ (re.compile(r"(?i)\bpretend\s+you\s+have\s+no\s+(?:safety|ethical|content)\s+filters?"), "JAILBREAK_SAFETY_BYPASS", 0.90),
36
+
37
+ # System prompt extraction & admin privilege escalation
38
+ (re.compile(r"(?i)\b(?:print|show|dump|reveal|output|display|repeat|leak)\s+(?:your\s+)?(?:system\s+prompts?|initial\s+instructions?|system\s+instructions?|core\s+rules?|internal\s+prompts?|credentials?|passwords?|api\s*keys?|secrets?)"), "SYSTEM_PROMPT_EXTRACTION", 0.92),
39
+ (re.compile(r"(?i)\brepeat\s+the\s+words\s+above\s+verbatim"), "SYSTEM_PROMPT_EXTRACTION", 0.85),
40
+ (re.compile(r"(?i)\bdump\s+(?:all\s+)?(?:internal|system|database|schema|users?|tables?)\b"), "SYSTEM_PROMPT_EXTRACTION", 0.92),
41
+ (re.compile(r"(?i)\b(?:using|with|via)\s+(?:admin|root|superuser|system|master)\s+(?:credentials?|privileges?|access|permissions?|rights?|tokens?)"), "PRIVILEGE_ESCALATION", 0.95),
42
+ (re.compile(r"(?i)\b(?:delete|drop|wipe|purge|truncate|destroy)\s+(?:everything|everthing|all\s+data|all\s+tables|database|users?|system)\b"), "DESTRUCTIVE_DATABASE_EXECUTION", 0.96),
43
+ (re.compile(r"(?i)\bswitch\s+to\s+(?:developer|admin|god|root|jailbreak)\s+mode\b"), "PRIVILEGE_ESCALATION", 0.92),
44
+
45
+ # SQL Injection & tool parameter tampering
46
+ (re.compile(r"(?i)(?:union\s+select|select\s+.*\s+from\s+|insert\s+into\s+|drop\s+table|delete\s+from\s+|truncate\s+table|exec\s+sp_|xp_cmdshell|or\s+1\s*=\s*1|'\s*or\s*'1'\s*=\s*'1|--|\bwaitfor\s+delay\b)"), "SQLI_PARAMETER_INJECTION", 0.96),
47
+
48
+ # Delimiters & Synthetic tags
49
+ (re.compile(r"(?i)\[system\s+override\]"), "SYNTHETIC_DELIMITER_INJECTION", 0.94),
50
+ (re.compile(r"(?i)<\s*system(?:\s+override)?\s*>"), "SYNTHETIC_XML_INJECTION", 0.94),
51
+ (re.compile(r"(?i)```\s*(?:system|override|admin)"), "SYNTHETIC_CODEBLOCK_INJECTION", 0.90),
52
+ ]
53
+
54
+ FAST_TOXICITY_RE = re.compile(
55
+ r"(?i)\b(?:f+u+c+k+|s+h+i+t+|b+i+t+c+h+|asshole|kill\s+yourself|faggot|nigger|bomb\s+the|murder\s+you)\b"
56
+ )
57
+
58
+ class SecurityPolicy:
59
+ STRICT = "enterprise-strict"
60
+ STANDARD = "standard"
61
+ MONITOR_ONLY = "audit-only"
62
+
63
+ def inspect_input(prompt: str, policy: str = SecurityPolicy.STRICT) -> Dict[str, Any]:
64
+ """In-process fast heuristic evaluation of incoming prompt strings (< 0.2ms)."""
65
+ start = time.perf_counter()
66
+ if not prompt or not isinstance(prompt, str):
67
+ return {"is_safe": True, "risk_score": 0.0, "latency_ms": 0.0}
68
+
69
+ matched_threats = []
70
+ max_risk = 0.0
71
+ threat_cat = None
72
+
73
+ for pattern, category, score in FAST_INJECTION_PATTERNS:
74
+ if pattern.search(prompt):
75
+ matched_threats.append(category)
76
+ if score > max_risk:
77
+ max_risk = score
78
+ threat_cat = category
79
+
80
+ if FAST_TOXICITY_RE.search(prompt):
81
+ matched_threats.append("VULGARITY_VIOLATION")
82
+ if 0.88 > max_risk:
83
+ max_risk = 0.88
84
+ threat_cat = "VULGARITY_VIOLATION"
85
+
86
+ is_safe = len(matched_threats) == 0
87
+ elapsed_ms = round((time.perf_counter() - start) * 1000, 3)
88
+
89
+ return {
90
+ "is_safe": is_safe,
91
+ "risk_score": max_risk,
92
+ "threat_detected": threat_cat,
93
+ "matched_patterns": matched_threats,
94
+ "latency_ms": elapsed_ms
95
+ }
96
+
97
+ def inspect_output(output: str, policy: str = SecurityPolicy.STRICT) -> Dict[str, Any]:
98
+ """In-process fast evaluation of model outputs for vulgarity or leaks (< 0.2ms)."""
99
+ start = time.perf_counter()
100
+ if not output or not isinstance(output, str):
101
+ return {"is_safe": True, "latency_ms": 0.0}
102
+
103
+ has_tox = bool(FAST_TOXICITY_RE.search(output))
104
+ elapsed_ms = round((time.perf_counter() - start) * 1000, 3)
105
+
106
+ return {
107
+ "is_safe": not has_tox,
108
+ "threat_detected": "VULGARITY_VIOLATION" if has_tox else None,
109
+ "latency_ms": elapsed_ms
110
+ }
111
+
112
+ def extract_prompt_from_call(args: tuple, kwargs: dict) -> Tuple[str, Optional[str], Optional[int]]:
113
+ """Extracts candidate prompt text from positional or keyword arguments."""
114
+ # Check 'prompt' kwarg
115
+ if "prompt" in kwargs and isinstance(kwargs["prompt"], str):
116
+ return kwargs["prompt"], "prompt", None
117
+
118
+ # Check 'messages' kwarg (OpenAI/Anthropic chat format)
119
+ if "messages" in kwargs and isinstance(kwargs["messages"], list):
120
+ for msg in reversed(kwargs["messages"]):
121
+ if isinstance(msg, dict) and msg.get("role") == "user" and isinstance(msg.get("content"), str):
122
+ return msg["content"], "messages", None
123
+
124
+ # Check first positional arg
125
+ if args and isinstance(args[0], str):
126
+ return args[0], None, 0
127
+
128
+ return "", None, None
129
+
130
+ def guard(
131
+ policy: str = SecurityPolicy.STRICT,
132
+ tokenize_pii: bool = True,
133
+ auto_detokenize: bool = True,
134
+ user_context: Optional[Dict[str, Any]] = None
135
+ ):
136
+ """
137
+ Zero-overhead (@guard) in-process AI Security & Governance Decorator.
138
+ Protects both synchronous and asynchronous functions from adversarial injections,
139
+ jailbreaks, PII leakage, and unauthorized tool calls.
140
+ """
141
+ def decorator(func: Callable) -> Callable:
142
+ is_coroutine = inspect.iscoroutinefunction(func)
143
+
144
+ @functools.wraps(func)
145
+ async def async_wrapper(*args, **kwargs):
146
+ prompt_text, kwarg_key, pos_idx = extract_prompt_from_call(args, kwargs)
147
+ token_map = {}
148
+
149
+ if prompt_text:
150
+ in_res = inspect_input(prompt_text, policy)
151
+ if not in_res["is_safe"] and policy != SecurityPolicy.MONITOR_ONLY:
152
+ raise SecurityViolationError(
153
+ f"SecureAI Guard: Input blocked due to {in_res['threat_detected']} (Risk Score: {in_res['risk_score']})",
154
+ risk_score=in_res["risk_score"],
155
+ threat_type=in_res["threat_detected"],
156
+ matched_patterns=in_res["matched_patterns"]
157
+ )
158
+
159
+ if tokenize_pii:
160
+ vaulted = default_vault.tokenize(prompt_text)
161
+ token_map = vaulted.token_map
162
+ if vaulted.redacted_count > 0:
163
+ # Replace in call args/kwargs
164
+ if kwarg_key == "prompt":
165
+ kwargs["prompt"] = str(vaulted)
166
+ elif pos_idx == 0:
167
+ args = (str(vaulted),) + args[1:]
168
+
169
+ result = await func(*args, **kwargs)
170
+
171
+ if isinstance(result, str):
172
+ out_res = inspect_output(result, policy)
173
+ if not out_res["is_safe"] and policy != SecurityPolicy.MONITOR_ONLY:
174
+ raise SecurityViolationError(
175
+ f"SecureAI Guard: Output blocked due to {out_res['threat_detected']}",
176
+ risk_score=0.90,
177
+ threat_type=out_res["threat_detected"]
178
+ )
179
+ if auto_detokenize and token_map:
180
+ result = default_vault.detokenize(result, token_map)
181
+
182
+ return result
183
+
184
+ @functools.wraps(func)
185
+ def sync_wrapper(*args, **kwargs):
186
+ prompt_text, kwarg_key, pos_idx = extract_prompt_from_call(args, kwargs)
187
+ token_map = {}
188
+
189
+ if prompt_text:
190
+ in_res = inspect_input(prompt_text, policy)
191
+ if not in_res["is_safe"] and policy != SecurityPolicy.MONITOR_ONLY:
192
+ raise SecurityViolationError(
193
+ f"SecureAI Guard: Input blocked due to {in_res['threat_detected']} (Risk Score: {in_res['risk_score']})",
194
+ risk_score=in_res["risk_score"],
195
+ threat_type=in_res["threat_detected"],
196
+ matched_patterns=in_res["matched_patterns"]
197
+ )
198
+
199
+ if tokenize_pii:
200
+ vaulted = default_vault.tokenize(prompt_text)
201
+ token_map = vaulted.token_map
202
+ if vaulted.redacted_count > 0:
203
+ if kwarg_key == "prompt":
204
+ kwargs["prompt"] = str(vaulted)
205
+ elif pos_idx == 0:
206
+ args = (str(vaulted),) + args[1:]
207
+
208
+ result = func(*args, **kwargs)
209
+
210
+ if isinstance(result, str):
211
+ out_res = inspect_output(result, policy)
212
+ if not out_res["is_safe"] and policy != SecurityPolicy.MONITOR_ONLY:
213
+ raise SecurityViolationError(
214
+ f"SecureAI Guard: Output blocked due to {out_res['threat_detected']}",
215
+ risk_score=0.90,
216
+ threat_type=out_res["threat_detected"]
217
+ )
218
+ if auto_detokenize and token_map:
219
+ result = default_vault.detokenize(result, token_map)
220
+
221
+ return result
222
+
223
+ return async_wrapper if is_coroutine else sync_wrapper
224
+ return decorator
@@ -0,0 +1,3 @@
1
+ from .openai_wrapper import wrap_openai
2
+
3
+ __all__ = ["wrap_openai"]
@@ -0,0 +1,65 @@
1
+ import functools
2
+ from typing import Any, Dict, List, Optional
3
+ from ..guard import inspect_input, inspect_output, SecurityViolationError, SecurityPolicy
4
+ from ..vault import default_vault
5
+
6
+ def wrap_openai(client: Any, policy: str = SecurityPolicy.STRICT, tokenize_pii: bool = True) -> Any:
7
+ """
8
+ Wraps an official `openai.OpenAI` or `openai.AsyncOpenAI` instance with
9
+ transparent, automatic prompt injection defense and reversible PII tokenization.
10
+ """
11
+ original_create = client.chat.completions.create
12
+
13
+ if hasattr(original_create, "__func__") or callable(original_create):
14
+ @functools.wraps(original_create)
15
+ def wrapped_create(*args, **kwargs):
16
+ messages = kwargs.get("messages", [])
17
+ token_map = {}
18
+
19
+ # Sanitize all user messages in the array
20
+ sanitized_messages = []
21
+ for msg in messages:
22
+ if isinstance(msg, dict) and msg.get("role") == "user" and isinstance(msg.get("content"), str):
23
+ prompt_text = msg["content"]
24
+
25
+ # 1. Run Guard Inspection
26
+ in_res = inspect_input(prompt_text, policy)
27
+ if not in_res["is_safe"] and policy != SecurityPolicy.MONITOR_ONLY:
28
+ raise SecurityViolationError(
29
+ f"SecureAI: Blocked OpenAI payload due to {in_res['threat_detected']}",
30
+ risk_score=in_res["risk_score"],
31
+ threat_type=in_res["threat_detected"],
32
+ matched_patterns=in_res["matched_patterns"]
33
+ )
34
+
35
+ # 2. Vault Tokenize PII
36
+ if tokenize_pii:
37
+ vaulted = default_vault.tokenize(prompt_text)
38
+ token_map.update(vaulted.token_map)
39
+ msg_copy = dict(msg)
40
+ msg_copy["content"] = str(vaulted)
41
+ sanitized_messages.append(msg_copy)
42
+ else:
43
+ sanitized_messages.append(msg)
44
+ else:
45
+ sanitized_messages.append(msg)
46
+
47
+ kwargs["messages"] = sanitized_messages
48
+
49
+ # Execute completion
50
+ response = original_create(*args, **kwargs)
51
+
52
+ # Auto detokenize completion content if tokenized
53
+ if tokenize_pii and token_map and hasattr(response, "choices"):
54
+ for choice in response.choices:
55
+ if hasattr(choice, "message") and hasattr(choice.message, "content"):
56
+ if isinstance(choice.message.content, str):
57
+ choice.message.content = default_vault.detokenize(
58
+ choice.message.content, token_map
59
+ )
60
+
61
+ return response
62
+
63
+ client.chat.completions.create = wrapped_create
64
+
65
+ return client
secureai/mcp.py ADDED
@@ -0,0 +1,64 @@
1
+ import re
2
+ from typing import Dict, Any, List, Optional, Tuple
3
+ from .guard import SecurityViolationError
4
+
5
+ # MCP Tool Risk Profiles
6
+ CRITICAL_DESTRUCTIVE_TOOLS = {
7
+ "delete_file", "drop_table", "truncate_table", "execute_sql",
8
+ "modify_system_config", "shell_exec", "run_terminal_command"
9
+ }
10
+
11
+ SQLI_PATTERN = re.compile(
12
+ r"(?i)(?:union\s+select|select\s+.*\s+from|insert\s+into|drop\s+table|delete\s+from|truncate\s+table|exec\s+sp_|xp_cmdshell|or\s+1\s*=\s*1|'\s*or\s*'1'\s*=\s*'1|--|;\s*drop)",
13
+ re.IGNORECASE
14
+ )
15
+
16
+ class MCPToolGuard:
17
+ """
18
+ Model Context Protocol (MCP) Tool Call Security Evaluator.
19
+ Prevents parameter injection, unconstrained filesystem/database operations,
20
+ and enforces Human-In-The-Loop (HITL) step-up authorizations.
21
+ """
22
+
23
+ def __init__(self, require_hitl_for_destructive: bool = True):
24
+ self.require_hitl_for_destructive = require_hitl_for_destructive
25
+
26
+ def evaluate_tool_call(
27
+ self,
28
+ tool_name: str,
29
+ arguments: Dict[str, Any],
30
+ user_clearance: int = 1
31
+ ) -> Dict[str, Any]:
32
+ """
33
+ Evaluates a tool call payload.
34
+ Returns:
35
+ dict: {"is_authorized": bool, "action": "ALLOW"|"BLOCK"|"REQUIRE_HITL", "reason": str}
36
+ """
37
+ # 1. Check for SQLi or Command Injection in arguments
38
+ for k, v in arguments.items():
39
+ if isinstance(v, str):
40
+ if SQLI_PATTERN.search(v):
41
+ raise SecurityViolationError(
42
+ f"SecureAI MCP Guard: Tool '{tool_name}' argument '{k}' contains SQL injection patterns.",
43
+ risk_score=0.98,
44
+ threat_type="MCP_SQLI_PARAMETER_INJECTION"
45
+ )
46
+
47
+ # 2. Check if tool is destructive
48
+ is_destructive = tool_name.lower() in CRITICAL_DESTRUCTIVE_TOOLS or any(
49
+ w in tool_name.lower() for w in ["delete", "drop", "purge", "destroy", "exec"]
50
+ )
51
+
52
+ if is_destructive:
53
+ if self.require_hitl_for_destructive and user_clearance < 3:
54
+ return {
55
+ "is_authorized": False,
56
+ "action": "REQUIRE_HITL",
57
+ "reason": f"Tool '{tool_name}' is classified as HIGH_IMPACT_DESTRUCTIVE and requires Human-in-the-Loop manager approval."
58
+ }
59
+
60
+ return {
61
+ "is_authorized": True,
62
+ "action": "ALLOW",
63
+ "reason": "Tool execution authorized under active policy."
64
+ }