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 +48 -0
- secureai/cli.py +92 -0
- secureai/client.py +177 -0
- secureai/guard.py +224 -0
- secureai/integrations/__init__.py +3 -0
- secureai/integrations/openai_wrapper.py +65 -0
- secureai/mcp.py +64 -0
- secureai/vault.py +82 -0
- secureai_sdk-1.0.0.dist-info/METADATA +209 -0
- secureai_sdk-1.0.0.dist-info/RECORD +20 -0
- secureai_sdk-1.0.0.dist-info/WHEEL +5 -0
- secureai_sdk-1.0.0.dist-info/entry_points.txt +2 -0
- secureai_sdk-1.0.0.dist-info/licenses/LICENSE +175 -0
- secureai_sdk-1.0.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +1 -0
- tests/test_client.py +36 -0
- tests/test_guard.py +51 -0
- tests/test_mcp.py +31 -0
- tests/test_openai_wrapper.py +45 -0
- tests/test_vault.py +26 -0
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,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
|
+
}
|