risk-mirror 1.0.2__tar.gz

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,78 @@
1
+ Metadata-Version: 2.1
2
+ Name: risk-mirror
3
+ Version: 1.0.2
4
+ Summary: Deterministic AI Safety Toolkit - Python SDK & CLI
5
+ Home-page: https://github.com/myProjectsRavi/risk-mirror-core
6
+ Author: RTN Labs
7
+ Author-email: support@risk-mirror.com
8
+ License: UNKNOWN
9
+ Keywords: ai-safety,pii,secrets,prompt-security,deterministic,cli
10
+ Platform: UNKNOWN
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Security
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.8
24
+ Description-Content-Type: text/markdown
25
+ Provides-Extra: dev
26
+
27
+ # Risk Mirror SDK - Python
28
+
29
+ Deterministic AI Safety Toolkit for prompt security.
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ pip install risk-mirror
35
+ ```
36
+
37
+ ## Quick Start
38
+
39
+ ```python
40
+ from risk_mirror import RiskMirror
41
+
42
+ # Initialize client
43
+ client = RiskMirror(api_key="your-api-key")
44
+
45
+ # Scan for safety issues
46
+ result = client.scan("Check this text for PII and secrets")
47
+
48
+ print(result.verdict) # SAFE, REVIEW, or HIGH_RISK
49
+ print(result.findings) # List of findings
50
+ print(result.safe_output) # Redacted text
51
+ ```
52
+
53
+ ## Features
54
+
55
+ - **PII Detection**: Email, phone, SSN, etc.
56
+ - **Secrets Detection**: API keys, tokens, passwords
57
+ - **Injection Detection**: Prompt injection attempts
58
+ - **Safe Share**: Burner-safe strings for sharing with AI
59
+ - **Zero Storage**: No content is stored
60
+ - **Deterministic**: Same input = same output
61
+
62
+ ## Safe Share
63
+
64
+ ```python
65
+ from risk_mirror import RiskMirror
66
+
67
+ client = RiskMirror(api_key="your-api-key")
68
+ result = client.safe_share("sk-ABCD1234-XYZ", mode="full")
69
+
70
+ print(result.safe_share_text)
71
+ print(result.audit_summary)
72
+ ```
73
+
74
+ ## License
75
+
76
+ MIT
77
+
78
+
@@ -0,0 +1,50 @@
1
+ # Risk Mirror SDK - Python
2
+
3
+ Deterministic AI Safety Toolkit for prompt security.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install risk-mirror
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```python
14
+ from risk_mirror import RiskMirror
15
+
16
+ # Initialize client
17
+ client = RiskMirror(api_key="your-api-key")
18
+
19
+ # Scan for safety issues
20
+ result = client.scan("Check this text for PII and secrets")
21
+
22
+ print(result.verdict) # SAFE, REVIEW, or HIGH_RISK
23
+ print(result.findings) # List of findings
24
+ print(result.safe_output) # Redacted text
25
+ ```
26
+
27
+ ## Features
28
+
29
+ - **PII Detection**: Email, phone, SSN, etc.
30
+ - **Secrets Detection**: API keys, tokens, passwords
31
+ - **Injection Detection**: Prompt injection attempts
32
+ - **Safe Share**: Burner-safe strings for sharing with AI
33
+ - **Zero Storage**: No content is stored
34
+ - **Deterministic**: Same input = same output
35
+
36
+ ## Safe Share
37
+
38
+ ```python
39
+ from risk_mirror import RiskMirror
40
+
41
+ client = RiskMirror(api_key="your-api-key")
42
+ result = client.safe_share("sk-ABCD1234-XYZ", mode="full")
43
+
44
+ print(result.safe_share_text)
45
+ print(result.audit_summary)
46
+ ```
47
+
48
+ ## License
49
+
50
+ MIT
@@ -0,0 +1,31 @@
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, safe_share
9
+ from .types import (
10
+ ScanResponse,
11
+ ScanPolicy,
12
+ Finding,
13
+ AuditSummary,
14
+ SafeShareResponse,
15
+ Verdict,
16
+ Severity,
17
+ )
18
+
19
+ __version__ = "1.0.2"
20
+ __all__ = [
21
+ "RiskMirror",
22
+ "RiskMirrorError",
23
+ "ScanResponse",
24
+ "ScanPolicy",
25
+ "Finding",
26
+ "AuditSummary",
27
+ "SafeShareResponse",
28
+ "Verdict",
29
+ "Severity",
30
+ "safe_share",
31
+ ]
@@ -0,0 +1,309 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Risk Mirror CLI - API-Based Safety Scanner
4
+ ===========================================
5
+ Scan prompts and files for PII, secrets, and injection risks.
6
+ All scans go through the Risk Mirror API for unified usage tracking.
7
+
8
+ Usage:
9
+ risk-mirror scan "Your prompt text here"
10
+ risk-mirror scan -f prompt.txt
11
+ risk-mirror scan -f prompt.txt --json
12
+ risk-mirror safe-share "sensitive string"
13
+ risk-mirror safe-share -f secrets.txt --mode full
14
+ risk-mirror --version
15
+ """
16
+
17
+ import argparse
18
+ import json
19
+ import os
20
+ import sys
21
+ from typing import Optional, List
22
+
23
+ from .client import RiskMirror, RiskMirrorError
24
+
25
+ CLI_VERSION = "1.0.2"
26
+
27
+ # Exit codes
28
+ EXIT_SAFE = 0
29
+ EXIT_RISK = 1
30
+ EXIT_ERROR = 2
31
+
32
+
33
+ def create_parser() -> argparse.ArgumentParser:
34
+ """Create argument parser."""
35
+ parser = argparse.ArgumentParser(
36
+ prog="risk-mirror",
37
+ description="Risk Mirror CLI - Deterministic AI Safety Scanner",
38
+ epilog="Examples:\n"
39
+ " risk-mirror scan \"Check this prompt\"\n"
40
+ " risk-mirror scan -f prompt.txt --json\n"
41
+ " RISK_MIRROR_API_KEY=rm_xxx risk-mirror scan \"text\"\n",
42
+ formatter_class=argparse.RawDescriptionHelpFormatter,
43
+ )
44
+
45
+ parser.add_argument(
46
+ "--version", "-V",
47
+ action="version",
48
+ version=f"risk-mirror CLI {CLI_VERSION}"
49
+ )
50
+
51
+ subparsers = parser.add_subparsers(dest="command", help="Commands")
52
+
53
+ # Scan command
54
+ scan_parser = subparsers.add_parser(
55
+ "scan",
56
+ help="Scan text or file for safety issues",
57
+ description="Scan text or file for PII, secrets, and prompt injection"
58
+ )
59
+
60
+ scan_parser.add_argument(
61
+ "text",
62
+ nargs="?",
63
+ help="Text to scan (use -f for file input)"
64
+ )
65
+
66
+ scan_parser.add_argument(
67
+ "-f", "--file",
68
+ help="File to scan"
69
+ )
70
+
71
+ scan_parser.add_argument(
72
+ "--api-key", "-k",
73
+ help="API key (or set RISK_MIRROR_API_KEY env var)"
74
+ )
75
+
76
+ scan_parser.add_argument(
77
+ "--json", "-j",
78
+ action="store_true",
79
+ help="Output as JSON"
80
+ )
81
+
82
+ scan_parser.add_argument(
83
+ "--quiet", "-q",
84
+ action="store_true",
85
+ help="Only output verdict (for CI/CD)"
86
+ )
87
+
88
+ # Safe Share command
89
+ safe_parser = subparsers.add_parser(
90
+ "safe-share",
91
+ help="Generate Safe Share burner text",
92
+ description="Create non-reversible, format-preserving safe share text"
93
+ )
94
+
95
+ safe_parser.add_argument(
96
+ "text",
97
+ nargs="?",
98
+ help="Text to transform (use -f for file input)"
99
+ )
100
+
101
+ safe_parser.add_argument(
102
+ "-f", "--file",
103
+ help="File to transform"
104
+ )
105
+
106
+ safe_parser.add_argument(
107
+ "--mode",
108
+ choices=["full", "selective"],
109
+ default="selective",
110
+ help="Replacement mode (default: selective)"
111
+ )
112
+
113
+ safe_parser.add_argument(
114
+ "--no-secrets",
115
+ action="store_true",
116
+ help="Disable secrets replacement in selective mode"
117
+ )
118
+
119
+ safe_parser.add_argument(
120
+ "--allow-valid",
121
+ action="store_true",
122
+ help="Allow valid-looking values (disables strict invalid generation)"
123
+ )
124
+
125
+ safe_parser.add_argument(
126
+ "--api-key", "-k",
127
+ help="API key (or set RISK_MIRROR_API_KEY env var)"
128
+ )
129
+
130
+ safe_parser.add_argument(
131
+ "--json", "-j",
132
+ action="store_true",
133
+ help="Output as JSON"
134
+ )
135
+
136
+ safe_parser.add_argument(
137
+ "--quiet", "-q",
138
+ action="store_true",
139
+ help="Only output the safe share text"
140
+ )
141
+
142
+ return parser
143
+
144
+
145
+ def format_result(result, as_json: bool = False) -> str:
146
+ """Format scan result for display."""
147
+ if as_json:
148
+ return json.dumps({
149
+ "verdict": result.verdict.value if hasattr(result.verdict, 'value') else str(result.verdict),
150
+ "safe_output": result.safe_output,
151
+ "findings_count": len(result.findings) if result.findings else 0,
152
+ }, indent=2)
153
+
154
+ verdict = result.verdict.value if hasattr(result.verdict, 'value') else str(result.verdict)
155
+ emoji = {"SAFE": "✅", "REVIEW": "⚠️", "BLOCK": "🚨"}.get(verdict, "❓")
156
+
157
+ lines = [
158
+ f"\n{emoji} Verdict: {verdict}",
159
+ ]
160
+
161
+ if result.findings:
162
+ lines.append(f"\n📋 Findings ({len(result.findings)}):")
163
+ for f in result.findings:
164
+ lines.append(f" • {f.get('category', 'unknown')}: {f.get('count', 1)} occurrence(s)")
165
+
166
+ if result.safe_output and result.safe_output != result.input_text:
167
+ lines.append(f"\n🔒 Safe Output:\n{result.safe_output[:500]}{'...' if len(result.safe_output) > 500 else ''}")
168
+
169
+ lines.append("\n🔐 Privacy: Stateless scan, no content stored")
170
+
171
+ return "\n".join(lines)
172
+
173
+
174
+ def main(args: Optional[List[str]] = None) -> int:
175
+ """Main CLI entry point."""
176
+ parser = create_parser()
177
+ parsed = parser.parse_args(args)
178
+
179
+ if not parsed.command:
180
+ parser.print_help()
181
+ return EXIT_ERROR
182
+
183
+ if parsed.command == "scan":
184
+ return handle_scan(parsed)
185
+ if parsed.command == "safe-share":
186
+ return handle_safe_share(parsed)
187
+
188
+ return EXIT_ERROR
189
+
190
+
191
+ def handle_scan(args: argparse.Namespace) -> int:
192
+ """Handle scan command."""
193
+ # Get API key
194
+ api_key = args.api_key or os.environ.get("RISK_MIRROR_API_KEY")
195
+
196
+ # Get input text
197
+ if args.file:
198
+ try:
199
+ with open(args.file, "r", encoding="utf-8") as f:
200
+ text = f.read()
201
+ except FileNotFoundError:
202
+ print(f"Error: File not found: {args.file}", file=sys.stderr)
203
+ return EXIT_ERROR
204
+
205
+
206
+ def handle_safe_share(args: argparse.Namespace) -> int:
207
+ """Handle safe-share command."""
208
+ api_key = args.api_key or os.environ.get("RISK_MIRROR_API_KEY")
209
+
210
+ if args.file:
211
+ try:
212
+ with open(args.file, "r", encoding="utf-8") as f:
213
+ text = f.read()
214
+ except FileNotFoundError:
215
+ print(f"Error: File not found: {args.file}", file=sys.stderr)
216
+ return EXIT_ERROR
217
+ except Exception as e:
218
+ print(f"Error reading file: {e}", file=sys.stderr)
219
+ return EXIT_ERROR
220
+ elif args.text:
221
+ text = args.text
222
+ else:
223
+ if sys.stdin.isatty():
224
+ print("Error: No input provided. Use -f FILE or provide text.", file=sys.stderr)
225
+ return EXIT_ERROR
226
+ text = sys.stdin.read()
227
+
228
+ if not text.strip():
229
+ print("Error: Empty input", file=sys.stderr)
230
+ return EXIT_ERROR
231
+
232
+ try:
233
+ client = RiskMirror(api_key=api_key)
234
+ result = client.safe_share(
235
+ text,
236
+ mode=args.mode,
237
+ include_secrets=not args.no_secrets,
238
+ strict_invalid=not args.allow_valid,
239
+ )
240
+
241
+ if args.quiet:
242
+ print(result.safe_share_text)
243
+ return EXIT_SAFE
244
+
245
+ if args.json:
246
+ print(json.dumps({
247
+ "safe_share_text": result.safe_share_text,
248
+ "mode": result.mode,
249
+ "audit_summary": result.audit_summary,
250
+ }, indent=2))
251
+ else:
252
+ print(result.safe_share_text)
253
+ if result.audit_summary:
254
+ print(f"\n🔐 Replaced spans: {result.audit_summary.get('spans_replaced', 0)}")
255
+ print(f"🧩 Replaced chars: {result.audit_summary.get('chars_replaced', 0)}")
256
+ print("\n🔐 Privacy: Stateless, no content stored")
257
+
258
+ return EXIT_SAFE
259
+ except RiskMirrorError as e:
260
+ print(f"API Error: {e.message}", file=sys.stderr)
261
+ if e.status_code == 401:
262
+ print("Hint: Set RISK_MIRROR_API_KEY or use --api-key", file=sys.stderr)
263
+ return EXIT_ERROR
264
+ except Exception as e:
265
+ print(f"Error: {e}", file=sys.stderr)
266
+ return EXIT_ERROR
267
+ except Exception as e:
268
+ print(f"Error reading file: {e}", file=sys.stderr)
269
+ return EXIT_ERROR
270
+ elif args.text:
271
+ text = args.text
272
+ else:
273
+ # Read from stdin
274
+ if sys.stdin.isatty():
275
+ print("Error: No input provided. Use -f FILE or provide text.", file=sys.stderr)
276
+ return EXIT_ERROR
277
+ text = sys.stdin.read()
278
+
279
+ if not text.strip():
280
+ print("Error: Empty input", file=sys.stderr)
281
+ return EXIT_ERROR
282
+
283
+ # Create client and scan
284
+ try:
285
+ client = RiskMirror(api_key=api_key)
286
+ result = client.scan(text)
287
+
288
+ if args.quiet:
289
+ verdict = result.verdict.value if hasattr(result.verdict, 'value') else str(result.verdict)
290
+ print(verdict)
291
+ else:
292
+ print(format_result(result, as_json=args.json))
293
+
294
+ # Return exit code based on verdict
295
+ verdict = result.verdict.value if hasattr(result.verdict, 'value') else str(result.verdict)
296
+ return EXIT_SAFE if verdict == "SAFE" else EXIT_RISK
297
+
298
+ except RiskMirrorError as e:
299
+ print(f"API Error: {e.message}", file=sys.stderr)
300
+ if e.status_code == 401:
301
+ print("Hint: Set RISK_MIRROR_API_KEY or use --api-key", file=sys.stderr)
302
+ return EXIT_ERROR
303
+ except Exception as e:
304
+ print(f"Error: {e}", file=sys.stderr)
305
+ return EXIT_ERROR
306
+
307
+
308
+ if __name__ == "__main__":
309
+ sys.exit(main())
@@ -0,0 +1,394 @@
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, SafeShareResponse, 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.2"
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 safe_share(
316
+ self,
317
+ input_text: str,
318
+ mode: str = "selective",
319
+ include_secrets: bool = True,
320
+ strict_invalid: bool = True
321
+ ) -> SafeShareResponse:
322
+ """
323
+ Generate Safe Share burner text (non-reversible).
324
+
325
+ Args:
326
+ input_text: Text to transform
327
+ mode: full or selective
328
+ include_secrets: Replace secrets in selective mode
329
+ strict_invalid: Force invalid CC/SSN/etc
330
+
331
+ Returns:
332
+ SafeShareResponse with safe_share_text and audit summary
333
+ """
334
+ if not input_text or not isinstance(input_text, str):
335
+ raise RiskMirrorError(
336
+ code="INVALID_INPUT",
337
+ message="Input must be a non-empty string",
338
+ retryable=False
339
+ )
340
+
341
+ response_data = self._request("/safe-share/text", "POST", {
342
+ "text": input_text,
343
+ "mode": mode,
344
+ "include_secrets": include_secrets,
345
+ "strict_invalid": strict_invalid,
346
+ })
347
+
348
+ return SafeShareResponse.from_dict(response_data)
349
+
350
+ def get_version(self) -> Dict[str, str]:
351
+ """
352
+ Get SDK and engine version.
353
+
354
+ US-0015: Rollback - version tracking
355
+ """
356
+ return {
357
+ "sdk": SDK_VERSION,
358
+ "engine": ENGINE_VERSION,
359
+ }
360
+
361
+
362
+ # ============ Quick Helpers ============
363
+ _default_client: Optional[RiskMirror] = None
364
+
365
+
366
+ def configure(**kwargs: Any) -> None:
367
+ """Configure the default client."""
368
+ global _default_client
369
+ _default_client = RiskMirror(**kwargs)
370
+
371
+
372
+ def scan(
373
+ input_text: str,
374
+ policy: Optional[ScanPolicy] = None,
375
+ mode: str = "default"
376
+ ) -> ScanResponse:
377
+ """Quick scan using default client."""
378
+ global _default_client
379
+ if _default_client is None:
380
+ _default_client = RiskMirror()
381
+ return _default_client.scan(input_text, policy, mode)
382
+
383
+
384
+ def safe_share(
385
+ input_text: str,
386
+ mode: str = "selective",
387
+ include_secrets: bool = True,
388
+ strict_invalid: bool = True
389
+ ) -> SafeShareResponse:
390
+ """Quick safe share using default client."""
391
+ global _default_client
392
+ if _default_client is None:
393
+ _default_client = RiskMirror()
394
+ return _default_client.safe_share(input_text, mode, include_secrets, strict_invalid)
@@ -0,0 +1,160 @@
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
+ )
136
+
137
+
138
+ @dataclass
139
+ class SafeShareResponse:
140
+ """Response from Safe Share operation."""
141
+ safe_share_text: str
142
+ mode: str
143
+ audit_summary: Dict[str, Any]
144
+ engine_version: str
145
+ privacy: Optional[Privacy] = None
146
+
147
+ @classmethod
148
+ def from_dict(cls, data: Dict[str, Any]) -> "SafeShareResponse":
149
+ privacy_data = data.get("privacy")
150
+ privacy = Privacy(
151
+ stateless=privacy_data.get("stateless", True),
152
+ content_logged=False,
153
+ ) if privacy_data else Privacy()
154
+ return cls(
155
+ safe_share_text=data.get("safe_share_text", ""),
156
+ mode=data.get("mode", "selective"),
157
+ audit_summary=data.get("audit_summary", {}),
158
+ engine_version=data.get("engine_version", ""),
159
+ privacy=privacy,
160
+ )
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.1
2
+ Name: risk-mirror
3
+ Version: 1.0.2
4
+ Summary: Deterministic AI Safety Toolkit - Python SDK & CLI
5
+ Home-page: https://github.com/myProjectsRavi/risk-mirror-core
6
+ Author: RTN Labs
7
+ Author-email: support@risk-mirror.com
8
+ License: UNKNOWN
9
+ Keywords: ai-safety,pii,secrets,prompt-security,deterministic,cli
10
+ Platform: UNKNOWN
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Security
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.8
24
+ Description-Content-Type: text/markdown
25
+ Provides-Extra: dev
26
+
27
+ # Risk Mirror SDK - Python
28
+
29
+ Deterministic AI Safety Toolkit for prompt security.
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ pip install risk-mirror
35
+ ```
36
+
37
+ ## Quick Start
38
+
39
+ ```python
40
+ from risk_mirror import RiskMirror
41
+
42
+ # Initialize client
43
+ client = RiskMirror(api_key="your-api-key")
44
+
45
+ # Scan for safety issues
46
+ result = client.scan("Check this text for PII and secrets")
47
+
48
+ print(result.verdict) # SAFE, REVIEW, or HIGH_RISK
49
+ print(result.findings) # List of findings
50
+ print(result.safe_output) # Redacted text
51
+ ```
52
+
53
+ ## Features
54
+
55
+ - **PII Detection**: Email, phone, SSN, etc.
56
+ - **Secrets Detection**: API keys, tokens, passwords
57
+ - **Injection Detection**: Prompt injection attempts
58
+ - **Safe Share**: Burner-safe strings for sharing with AI
59
+ - **Zero Storage**: No content is stored
60
+ - **Deterministic**: Same input = same output
61
+
62
+ ## Safe Share
63
+
64
+ ```python
65
+ from risk_mirror import RiskMirror
66
+
67
+ client = RiskMirror(api_key="your-api-key")
68
+ result = client.safe_share("sk-ABCD1234-XYZ", mode="full")
69
+
70
+ print(result.safe_share_text)
71
+ print(result.audit_summary)
72
+ ```
73
+
74
+ ## License
75
+
76
+ MIT
77
+
78
+
@@ -0,0 +1,12 @@
1
+ README.md
2
+ setup.py
3
+ risk_mirror/__init__.py
4
+ risk_mirror/cli.py
5
+ risk_mirror/client.py
6
+ risk_mirror/types.py
7
+ risk_mirror.egg-info/PKG-INFO
8
+ risk_mirror.egg-info/SOURCES.txt
9
+ risk_mirror.egg-info/dependency_links.txt
10
+ risk_mirror.egg-info/entry_points.txt
11
+ risk_mirror.egg-info/requires.txt
12
+ risk_mirror.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ risk-mirror = risk_mirror.cli:main
3
+
@@ -0,0 +1,5 @@
1
+
2
+ [dev]
3
+ pytest
4
+ pytest-cov
5
+ mypy
@@ -0,0 +1 @@
1
+ risk_mirror
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,44 @@
1
+ """
2
+ Risk Mirror SDK - Python Package Setup
3
+ """
4
+ from setuptools import setup, find_packages
5
+
6
+ with open("README.md", "r", encoding="utf-8") as f:
7
+ long_description = f.read() if f else ""
8
+
9
+ setup(
10
+ name="risk-mirror",
11
+ version="1.0.2",
12
+ author="RTN Labs",
13
+ author_email="support@risk-mirror.com",
14
+ description="Deterministic AI Safety Toolkit - Python SDK & CLI",
15
+ long_description=long_description,
16
+ long_description_content_type="text/markdown",
17
+ url="https://github.com/myProjectsRavi/risk-mirror-core",
18
+ packages=find_packages(),
19
+ classifiers=[
20
+ "Development Status :: 5 - Production/Stable",
21
+ "Intended Audience :: Developers",
22
+ "License :: OSI Approved :: MIT License",
23
+ "Operating System :: OS Independent",
24
+ "Programming Language :: Python :: 3",
25
+ "Programming Language :: Python :: 3.8",
26
+ "Programming Language :: Python :: 3.9",
27
+ "Programming Language :: Python :: 3.10",
28
+ "Programming Language :: Python :: 3.11",
29
+ "Programming Language :: Python :: 3.12",
30
+ "Topic :: Security",
31
+ "Topic :: Software Development :: Libraries :: Python Modules",
32
+ ],
33
+ python_requires=">=3.8",
34
+ install_requires=[], # Zero dependencies - uses stdlib only
35
+ extras_require={
36
+ "dev": ["pytest", "pytest-cov", "mypy"],
37
+ },
38
+ entry_points={
39
+ "console_scripts": [
40
+ "risk-mirror=risk_mirror.cli:main",
41
+ ],
42
+ },
43
+ keywords=["ai-safety", "pii", "secrets", "prompt-security", "deterministic", "cli"],
44
+ )