risk-mirror 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.
@@ -0,0 +1,28 @@
1
+ """
2
+ Risk Mirror SDK - Python
3
+ ========================
4
+ Deterministic, stateless AI safety toolkit
5
+
6
+ US-0001 to US-0020: SDKs (JS/Python) feature
7
+ """
8
+ from .client import RiskMirror, RiskMirrorError
9
+ from .types import (
10
+ ScanResponse,
11
+ ScanPolicy,
12
+ Finding,
13
+ AuditSummary,
14
+ Verdict,
15
+ Severity,
16
+ )
17
+
18
+ __version__ = "1.0.0"
19
+ __all__ = [
20
+ "RiskMirror",
21
+ "RiskMirrorError",
22
+ "ScanResponse",
23
+ "ScanPolicy",
24
+ "Finding",
25
+ "AuditSummary",
26
+ "Verdict",
27
+ "Severity",
28
+ ]
risk_mirror/client.py ADDED
@@ -0,0 +1,346 @@
1
+ """
2
+ Risk Mirror SDK - Python Client
3
+ ================================
4
+ Deterministic, stateless AI safety toolkit
5
+ Drop-in integration for prompt security
6
+
7
+ US-0001: Core Behavior
8
+ US-0002: Policy Controls
9
+ US-0006: Edge Cases
10
+ US-0007: Performance
11
+ US-0008: Security
12
+ US-0009: Audit Evidence
13
+ US-0010: Privacy (no storage)
14
+ US-0011: Compliance
15
+ US-0012: Rate Limits
16
+ US-0013: Logging
17
+ US-0015: Rollback
18
+ US-0020: Error Handling
19
+ """
20
+ import time
21
+ import logging
22
+ from typing import Optional, Callable, Dict, Any
23
+ from urllib.request import Request, urlopen
24
+ from urllib.error import HTTPError, URLError
25
+ import json
26
+
27
+ from .types import ScanResponse, ScanPolicy, OptimizeResponse, Verdict
28
+
29
+ # Constants
30
+ DEFAULT_BASE_URL = "https://risk-mirror-auth.anonymous617461746174.workers.dev"
31
+ DEFAULT_TIMEOUT = 30
32
+ DEFAULT_RETRIES = 3
33
+ SDK_VERSION = "1.0.0"
34
+ ENGINE_VERSION = "6.0-ULTRA"
35
+
36
+ logger = logging.getLogger("risk_mirror")
37
+
38
+
39
+ class RiskMirrorError(Exception):
40
+ """Base exception for Risk Mirror SDK.
41
+
42
+ US-0020: Error Handling
43
+ """
44
+ def __init__(
45
+ self,
46
+ code: str,
47
+ message: str,
48
+ retryable: bool = False,
49
+ status_code: Optional[int] = None
50
+ ):
51
+ super().__init__(message)
52
+ self.code = code
53
+ self.message = message
54
+ self.retryable = retryable
55
+ self.status_code = status_code
56
+
57
+
58
+ class RiskMirror:
59
+ """
60
+ Risk Mirror SDK Client
61
+ ======================
62
+ Deterministic AI safety scanning with zero content storage.
63
+
64
+ Example:
65
+ >>> client = RiskMirror(api_key="your-key")
66
+ >>> result = client.scan("Check this prompt for safety")
67
+ >>> print(result.verdict)
68
+ 'SAFE'
69
+
70
+ US-0001: Core Behavior
71
+ """
72
+
73
+ def __init__(
74
+ self,
75
+ base_url: str = DEFAULT_BASE_URL,
76
+ api_key: Optional[str] = None,
77
+ timeout: int = DEFAULT_TIMEOUT,
78
+ retries: int = DEFAULT_RETRIES,
79
+ debug: bool = False,
80
+ ):
81
+ """
82
+ Initialize the Risk Mirror client.
83
+
84
+ Args:
85
+ base_url: API base URL
86
+ api_key: Optional API key for authentication
87
+ timeout: Request timeout in seconds
88
+ retries: Number of retries on failure
89
+ debug: Enable debug logging
90
+ """
91
+ self.base_url = base_url.rstrip("/")
92
+ self.api_key = api_key
93
+ self.timeout = timeout
94
+ self.retries = retries
95
+ self.debug = debug
96
+ self._telemetry_callback: Optional[Callable[[Dict[str, Any]], None]] = None
97
+
98
+ if debug:
99
+ logging.basicConfig(level=logging.DEBUG)
100
+
101
+ def on_telemetry(self, callback: Callable[[Dict[str, Any]], None]) -> None:
102
+ """
103
+ Set telemetry callback (receives no content, only metadata).
104
+
105
+ US-0018: Analytics without storing content
106
+ """
107
+ self._telemetry_callback = callback
108
+
109
+ def _log(self, msg: str, *args: Any) -> None:
110
+ if self.debug:
111
+ logger.debug(f"[RiskMirror] {msg}", *args)
112
+
113
+ def _request(
114
+ self,
115
+ endpoint: str,
116
+ method: str = "POST",
117
+ body: Optional[Dict[str, Any]] = None
118
+ ) -> Dict[str, Any]:
119
+ """Make HTTP request with retry logic.
120
+
121
+ US-0012: Rate Limits
122
+ US-0020: Error Handling
123
+ """
124
+ url = f"{self.base_url}{endpoint}"
125
+ headers = {
126
+ "Content-Type": "application/json",
127
+ "X-SDK-Version": SDK_VERSION,
128
+ }
129
+
130
+ if self.api_key:
131
+ headers["X-API-Key"] = self.api_key
132
+
133
+ last_error: Optional[Exception] = None
134
+
135
+ for attempt in range(self.retries + 1):
136
+ try:
137
+ self._log(f"Request attempt {attempt + 1}: {method} {endpoint}")
138
+
139
+ data = json.dumps(body).encode("utf-8") if body else None
140
+ req = Request(url, data=data, headers=headers, method=method)
141
+
142
+ with urlopen(req, timeout=self.timeout) as response:
143
+ return json.loads(response.read().decode("utf-8"))
144
+
145
+ except HTTPError as e:
146
+ last_error = e
147
+
148
+ # US-0012: Rate limit handling
149
+ if e.code == 429:
150
+ retry_after = e.headers.get("Retry-After", "1")
151
+ wait_secs = int(retry_after)
152
+ self._log(f"Rate limited, waiting {wait_secs}s")
153
+ time.sleep(wait_secs)
154
+ continue
155
+
156
+ # Retryable server errors
157
+ if e.code >= 500 and attempt < self.retries:
158
+ wait_secs = min(2 ** attempt, 10)
159
+ self._log(f"Server error {e.code}, retry in {wait_secs}s")
160
+ time.sleep(wait_secs)
161
+ continue
162
+
163
+ raise RiskMirrorError(
164
+ code="API_ERROR",
165
+ message=f"API returned {e.code}: {e.read().decode('utf-8', errors='ignore')}",
166
+ retryable=e.code >= 500,
167
+ status_code=e.code
168
+ )
169
+
170
+ except URLError as e:
171
+ last_error = e
172
+ if attempt < self.retries:
173
+ wait_secs = min(2 ** attempt, 10)
174
+ self._log(f"Network error, retry in {wait_secs}s: {e.reason}")
175
+ time.sleep(wait_secs)
176
+ continue
177
+ raise RiskMirrorError(
178
+ code="NETWORK_ERROR",
179
+ message=str(e.reason),
180
+ retryable=True
181
+ )
182
+ except Exception as e:
183
+ last_error = e
184
+ raise RiskMirrorError(
185
+ code="UNKNOWN_ERROR",
186
+ message=str(e),
187
+ retryable=False
188
+ )
189
+
190
+ raise last_error or RiskMirrorError("UNKNOWN_ERROR", "Request failed", False)
191
+
192
+ def scan(
193
+ self,
194
+ input_text: str,
195
+ policy: Optional[ScanPolicy] = None,
196
+ mode: str = "default"
197
+ ) -> ScanResponse:
198
+ """
199
+ Scan input for safety issues.
200
+
201
+ US-0001: Core behavior - deterministic scanning
202
+ US-0002: Policy controls - configurable detection
203
+ US-0010: Privacy - no content storage
204
+
205
+ Args:
206
+ input_text: Text to scan
207
+ policy: Optional policy configuration
208
+ mode: Scan mode (default, strict, paranoid)
209
+
210
+ Returns:
211
+ ScanResponse with verdict, findings, and safe_output
212
+
213
+ Raises:
214
+ RiskMirrorError: On validation or API errors
215
+ """
216
+ start = time.perf_counter()
217
+
218
+ # US-0006: Edge cases - input validation
219
+ if not input_text or not isinstance(input_text, str):
220
+ raise RiskMirrorError(
221
+ code="INVALID_INPUT",
222
+ message="Input must be a non-empty string",
223
+ retryable=False
224
+ )
225
+
226
+ # US-0008: Security - size limit
227
+ max_length = policy.max_length if policy else 100000
228
+ if len(input_text) > max_length:
229
+ raise RiskMirrorError(
230
+ code="INPUT_TOO_LARGE",
231
+ message=f"Input exceeds maximum length of {max_length} characters",
232
+ retryable=False
233
+ )
234
+
235
+ # Build request
236
+ policy = policy or ScanPolicy()
237
+ request_body = {
238
+ "prompt": input_text,
239
+ "policy": {
240
+ "detect_pii": policy.pii,
241
+ "detect_secrets": policy.secrets,
242
+ "detect_injection": policy.injection,
243
+ },
244
+ }
245
+
246
+ if policy.pii_classes:
247
+ request_body["policy"]["pii_classes"] = policy.pii_classes
248
+
249
+ response_data = self._request("/firewall/audit", "POST", request_body)
250
+
251
+ latency_ms = (time.perf_counter() - start) * 1000
252
+ response_data["latency_ms"] = latency_ms
253
+ response_data["privacy"] = {"stateless": True, "content_logged": False}
254
+
255
+ # US-0018: Analytics telemetry (no content)
256
+ if self._telemetry_callback:
257
+ self._telemetry_callback({
258
+ "operation": "scan",
259
+ "verdict": response_data.get("verdict", "SAFE"),
260
+ "findings_count": len(response_data.get("findings", [])),
261
+ "latency_ms": latency_ms,
262
+ "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
263
+ })
264
+
265
+ return ScanResponse.from_dict(response_data)
266
+
267
+ def audit(
268
+ self,
269
+ input_text: str,
270
+ policy: Optional[ScanPolicy] = None
271
+ ) -> ScanResponse:
272
+ """
273
+ Audit scan for compliance reporting.
274
+
275
+ US-0009: Audit Evidence
276
+
277
+ Args:
278
+ input_text: Text to audit
279
+ policy: Optional policy configuration
280
+
281
+ Returns:
282
+ ScanResponse with detailed audit evidence
283
+ """
284
+ return self.scan(input_text, policy, mode="strict")
285
+
286
+ def optimize(
287
+ self,
288
+ input_text: str,
289
+ mode: str = "compress"
290
+ ) -> OptimizeResponse:
291
+ """
292
+ Optimize prompt for token efficiency.
293
+
294
+ Args:
295
+ input_text: Text to optimize
296
+ mode: compress, refine, or strip
297
+
298
+ Returns:
299
+ OptimizeResponse with optimized text
300
+ """
301
+ if not input_text or not isinstance(input_text, str):
302
+ raise RiskMirrorError(
303
+ code="INVALID_INPUT",
304
+ message="Input must be a non-empty string",
305
+ retryable=False
306
+ )
307
+
308
+ response_data = self._request("/optimize/prompt", "POST", {
309
+ "prompt": input_text,
310
+ "mode": mode,
311
+ })
312
+
313
+ return OptimizeResponse.from_dict(response_data)
314
+
315
+ def get_version(self) -> Dict[str, str]:
316
+ """
317
+ Get SDK and engine version.
318
+
319
+ US-0015: Rollback - version tracking
320
+ """
321
+ return {
322
+ "sdk": SDK_VERSION,
323
+ "engine": ENGINE_VERSION,
324
+ }
325
+
326
+
327
+ # ============ Quick Helpers ============
328
+ _default_client: Optional[RiskMirror] = None
329
+
330
+
331
+ def configure(**kwargs: Any) -> None:
332
+ """Configure the default client."""
333
+ global _default_client
334
+ _default_client = RiskMirror(**kwargs)
335
+
336
+
337
+ def scan(
338
+ input_text: str,
339
+ policy: Optional[ScanPolicy] = None,
340
+ mode: str = "default"
341
+ ) -> ScanResponse:
342
+ """Quick scan using default client."""
343
+ global _default_client
344
+ if _default_client is None:
345
+ _default_client = RiskMirror()
346
+ return _default_client.scan(input_text, policy, mode)
risk_mirror/types.py ADDED
@@ -0,0 +1,135 @@
1
+ """
2
+ Risk Mirror SDK - Type Definitions
3
+ ==================================
4
+ Dataclasses for SDK request/response models
5
+
6
+ US-0004: API Surface
7
+ US-0005: Data Model
8
+ """
9
+ from dataclasses import dataclass, field
10
+ from typing import List, Optional, Dict, Any, Literal
11
+ from enum import Enum
12
+
13
+
14
+ class Verdict(str, Enum):
15
+ SAFE = "SAFE"
16
+ REVIEW = "REVIEW"
17
+ HIGH_RISK = "HIGH_RISK"
18
+
19
+
20
+ class Severity(str, Enum):
21
+ LOW = "LOW"
22
+ MED = "MED"
23
+ HIGH = "HIGH"
24
+
25
+
26
+ @dataclass
27
+ class Finding:
28
+ """A single finding from the scan."""
29
+ category: str
30
+ severity: Severity
31
+ count: int
32
+ match: Optional[str] = None
33
+
34
+
35
+ @dataclass
36
+ class AuditSummary:
37
+ """Summary of modifications made."""
38
+ phrases_removed: int = 0
39
+ pii_redacted: int = 0
40
+ secrets_masked: int = 0
41
+ injections_blocked: int = 0
42
+
43
+
44
+ @dataclass
45
+ class ScanPolicy:
46
+ """Configuration for scan behavior.
47
+
48
+ US-0002: Policy Controls
49
+ """
50
+ pii: bool = True
51
+ secrets: bool = True
52
+ injection: bool = True
53
+ pii_classes: Optional[List[str]] = None
54
+ max_length: int = 100000
55
+
56
+
57
+ @dataclass
58
+ class Privacy:
59
+ """Privacy guarantees."""
60
+ stateless: bool = True
61
+ content_logged: bool = False
62
+
63
+
64
+ @dataclass
65
+ class ScanResponse:
66
+ """Response from scan operation.
67
+
68
+ US-0009: Audit Evidence
69
+ US-0010: Privacy
70
+ US-0011: Compliance
71
+ """
72
+ verdict: Verdict
73
+ findings: List[Finding]
74
+ safe_output: str
75
+ audit_summary: AuditSummary
76
+ policy_hash: str
77
+ engine_version: str
78
+ latency_ms: float
79
+ privacy: Optional[Privacy] = None
80
+ compliance_tags: List[str] = field(default_factory=list)
81
+
82
+ @classmethod
83
+ def from_dict(cls, data: Dict[str, Any]) -> "ScanResponse":
84
+ """Create ScanResponse from API response dict."""
85
+ findings = [
86
+ Finding(
87
+ category=f.get("category", "unknown"),
88
+ severity=Severity(f.get("severity", "LOW")),
89
+ count=f.get("count", 1),
90
+ match=f.get("match"),
91
+ )
92
+ for f in data.get("findings", [])
93
+ ]
94
+
95
+ audit_data = data.get("audit_summary", {})
96
+ audit_summary = AuditSummary(
97
+ phrases_removed=audit_data.get("phrases_removed", 0),
98
+ pii_redacted=audit_data.get("pii_redacted", 0),
99
+ secrets_masked=audit_data.get("secrets_masked", 0),
100
+ injections_blocked=audit_data.get("injections_blocked", 0),
101
+ )
102
+
103
+ privacy_data = data.get("privacy")
104
+ privacy = Privacy(
105
+ stateless=privacy_data.get("stateless", True),
106
+ content_logged=privacy_data.get("content_logged", False),
107
+ ) if privacy_data else Privacy()
108
+
109
+ return cls(
110
+ verdict=Verdict(data.get("verdict", "SAFE")),
111
+ findings=findings,
112
+ safe_output=data.get("safe_output", ""),
113
+ audit_summary=audit_summary,
114
+ policy_hash=data.get("policy_hash", ""),
115
+ engine_version=data.get("engine_version", "6.0-ULTRA"),
116
+ latency_ms=data.get("latency_ms", 0.0),
117
+ privacy=privacy,
118
+ compliance_tags=data.get("compliance_tags", []),
119
+ )
120
+
121
+
122
+ @dataclass
123
+ class OptimizeResponse:
124
+ """Response from optimize operation."""
125
+ optimized: str
126
+ tokens_saved: int
127
+ compression_ratio: float
128
+
129
+ @classmethod
130
+ def from_dict(cls, data: Dict[str, Any]) -> "OptimizeResponse":
131
+ return cls(
132
+ optimized=data.get("optimized", ""),
133
+ tokens_saved=data.get("tokens_saved", 0),
134
+ compression_ratio=data.get("compression_ratio", 1.0),
135
+ )
@@ -0,0 +1,74 @@
1
+ Metadata-Version: 2.4
2
+ Name: risk-mirror
3
+ Version: 1.0.0
4
+ Summary: Deterministic AI Safety Toolkit - Python SDK
5
+ Home-page: https://github.com/myProjectsRavi/risk-mirror-core
6
+ Author: RTN Labs
7
+ Author-email: support@risk-mirror.com
8
+ Keywords: ai-safety,pii,secrets,prompt-security,deterministic
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Security
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.8
22
+ Description-Content-Type: text/markdown
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest; extra == "dev"
25
+ Requires-Dist: pytest-cov; extra == "dev"
26
+ Requires-Dist: mypy; extra == "dev"
27
+ Dynamic: author
28
+ Dynamic: author-email
29
+ Dynamic: classifier
30
+ Dynamic: description
31
+ Dynamic: description-content-type
32
+ Dynamic: home-page
33
+ Dynamic: keywords
34
+ Dynamic: provides-extra
35
+ Dynamic: requires-python
36
+ Dynamic: summary
37
+
38
+ # Risk Mirror SDK - Python
39
+
40
+ Deterministic AI Safety Toolkit for prompt security.
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ pip install risk-mirror
46
+ ```
47
+
48
+ ## Quick Start
49
+
50
+ ```python
51
+ from risk_mirror import RiskMirror
52
+
53
+ # Initialize client
54
+ client = RiskMirror(api_key="your-api-key")
55
+
56
+ # Scan for safety issues
57
+ result = client.scan("Check this text for PII and secrets")
58
+
59
+ print(result.verdict) # SAFE, REVIEW, or HIGH_RISK
60
+ print(result.findings) # List of findings
61
+ print(result.safe_output) # Redacted text
62
+ ```
63
+
64
+ ## Features
65
+
66
+ - **PII Detection**: Email, phone, SSN, etc.
67
+ - **Secrets Detection**: API keys, tokens, passwords
68
+ - **Injection Detection**: Prompt injection attempts
69
+ - **Zero Storage**: No content is stored
70
+ - **Deterministic**: Same input = same output
71
+
72
+ ## License
73
+
74
+ MIT
@@ -0,0 +1,7 @@
1
+ risk_mirror/__init__.py,sha256=-rkpepWwQeJ8LLYUNg8xwWIUYrZ0g7q6jAuOHw5vVbE,492
2
+ risk_mirror/client.py,sha256=Cge24tZukgPYKHQASYlDuAhkoxxrALHyk54ar4uJ4ME,10764
3
+ risk_mirror/types.py,sha256=-QLCxNTRBGXR8cSHrDRl7Z97sPfU2OaM7SXB3wZtU0Q,3599
4
+ risk_mirror-1.0.0.dist-info/METADATA,sha256=GarpjZrNHW-ci8hXsNyp44IcZLT7XJ-TG2-ui_FoSQk,2066
5
+ risk_mirror-1.0.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
6
+ risk_mirror-1.0.0.dist-info/top_level.txt,sha256=BtKjDWugw6OoRR_DSXHJ0qxiqQxcul69MFko0szkZ-I,12
7
+ risk_mirror-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ risk_mirror