| """ |
| TSZ (Thyris Safe Zone) - Interactive Demo |
| ========================================== |
| GitHub: https://github.com/thyrisAI/safe-zone |
| Website: https://thyris.ai |
| """ |
|
|
| import gradio as gr |
| import re |
| import uuid |
| import hashlib |
| from dataclasses import dataclass |
| from typing import List, Tuple |
|
|
|
|
| @dataclass |
| class Detection: |
| type: str |
| value: str |
| placeholder: str |
| start: int |
| end: int |
| confidence: str |
| mask_id: str |
|
|
|
|
| class TSZDetector: |
| PATTERNS = { |
| "EMAIL": { |
| "pattern": r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b', |
| "description": "Email addresses" |
| }, |
| "PHONE": { |
| "pattern": r'\b(?:\+?1[-.\s]?)?\(?[0-9]{3}\)?[-.\s]?[0-9]{3}[-.\s]?[0-9]{4}\b', |
| "description": "Phone numbers (US format)" |
| }, |
| "PHONE_TR": { |
| "pattern": r'\b(?:\+90|0)?[-.\s]?[0-9]{3}[-.\s]?[0-9]{3}[-.\s]?[0-9]{2}[-.\s]?[0-9]{2}\b', |
| "description": "Phone numbers (Turkey format)" |
| }, |
| "CREDIT_CARD": { |
| "pattern": r'\b(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|6(?:011|5[0-9]{2})[0-9]{12})\b', |
| "description": "Credit card numbers" |
| }, |
| "SSN": { |
| "pattern": r'\b[0-9]{3}-[0-9]{2}-[0-9]{4}\b', |
| "description": "Social Security Numbers (US)" |
| }, |
| "TC_KIMLIK": { |
| "pattern": r'\b[1-9][0-9]{10}\b', |
| "description": "Turkish National ID" |
| }, |
| "IBAN": { |
| "pattern": r'\b[A-Z]{2}[0-9]{2}[A-Z0-9]{4}[0-9]{7}([A-Z0-9]?){0,16}\b', |
| "description": "International Bank Account Numbers" |
| }, |
| "IP_ADDRESS": { |
| "pattern": r'\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b', |
| "description": "IPv4 addresses" |
| }, |
| "API_KEY": { |
| "pattern": r'\b(?:sk-[a-zA-Z0-9]{32,}|api[_-]?key[_-]?[=:]\s*["\']?[a-zA-Z0-9]{16,}["\']?)\b', |
| "description": "API keys and secrets" |
| }, |
| "AWS_KEY": { |
| "pattern": r'\b(?:AKIA|ABIA|ACCA|ASIA)[A-Z0-9]{16}\b', |
| "description": "AWS Access Keys" |
| }, |
| "PASSWORD": { |
| "pattern": r'(?:password|passwd|pwd)[_\s]*[=:]\s*["\']?[^\s"\']{4,}["\']?', |
| "description": "Passwords in config/code" |
| }, |
| "JWT_TOKEN": { |
| "pattern": r'\beyJ[A-Za-z0-9-_]+\.eyJ[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\b', |
| "description": "JSON Web Tokens" |
| } |
| } |
|
|
| def _generate_mask_id(self, value: str) -> str: |
| """Generate a short unique mask ID based on value hash""" |
| hash_val = hashlib.md5(value.encode()).hexdigest()[:6] |
| return hash_val |
|
|
| def detect(self, text: str, rid: str = "NO-RID") -> Tuple[List[Detection], str]: |
| detections = [] |
| redacted_text = text |
| type_counters = {} |
|
|
| for pii_type, config in self.PATTERNS.items(): |
| pattern = config["pattern"] |
| for match in re.finditer(pattern, text, re.IGNORECASE): |
| value = match.group() |
| if pii_type == "TC_KIMLIK" and len(value) != 11: |
| continue |
|
|
| if pii_type not in type_counters: |
| type_counters[pii_type] = 0 |
| type_counters[pii_type] += 1 |
|
|
| mask_id = self._generate_mask_id(value) |
| placeholder = f"[{rid}_{pii_type}_{mask_id}]" |
|
|
| detections.append(Detection( |
| type=pii_type, |
| value=value, |
| placeholder=placeholder, |
| start=match.start(), |
| end=match.end(), |
| confidence="HIGH" if len(value) > 8 else "MEDIUM", |
| mask_id=mask_id |
| )) |
|
|
| detections.sort(key=lambda x: x.start, reverse=True) |
| for det in detections: |
| redacted_text = redacted_text[:det.start] + det.placeholder + redacted_text[det.end:] |
|
|
| detections.sort(key=lambda x: x.start) |
| return detections, redacted_text |
|
|
|
|
| detector = TSZDetector() |
|
|
|
|
| def analyze_text(text: str, rid: str) -> Tuple[str, str, str, str]: |
| if not text.strip(): |
| return "", "Please enter some text to analyze.", "", "" |
|
|
| if not rid.strip(): |
| rid = f"TSZ-{uuid.uuid4().hex[:8].upper()}" |
|
|
| detections, redacted_text = detector.detect(text, rid) |
|
|
| if not detections: |
| return text, "No sensitive information detected.", "**0** PII entities found", f"Request ID: `{rid}`" |
|
|
| report_lines = ["### Detected Entities\n"] |
| by_type = {} |
| for det in detections: |
| if det.type not in by_type: |
| by_type[det.type] = [] |
| by_type[det.type].append(det) |
|
|
| for pii_type, items in by_type.items(): |
| desc = detector.PATTERNS[pii_type]["description"] |
| report_lines.append(f"\n**{pii_type}** ({desc})") |
| for item in items: |
| masked_value = item.value[:3] + "***" + item.value[-2:] if len(item.value) > 5 else "***" |
| report_lines.append(f"- `{masked_value}` β `{item.placeholder}`") |
| report_lines.append(f" - Mask ID: `{item.mask_id}` | Confidence: {item.confidence}") |
|
|
| report = "\n".join(report_lines) |
| stats = f"**{len(detections)}** PII entities found across **{len(by_type)}** categories" |
| rid_display = f"Request ID: `{rid}`" |
|
|
| return redacted_text, report, stats, rid_display |
|
|
|
|
| EXAMPLES = [ |
| ["Hi, my name is John Smith and my email is john.smith@company.com. You can reach me at +1-555-123-4567. My SSN is 123-45-6789.", "REQ-001"], |
| ["Customer Order #12345\nName: Jane Doe\nEmail: jane.doe@example.org\nPhone: (555) 987-6543\nCredit Card: 4532015112830366", "ORDER-12345"], |
| ["API Configuration:\napi_key = \"sk-1234567890abcdefghijklmnopqrstuvwxyz\"\nAWS_ACCESS_KEY: AKIAIOSFODNN7EXAMPLE\npassword: \"super_secret_123\"", "CONFIG-SCAN"], |
| ["Merhaba, ben Ahmet Yilmaz. TC Kimlik numaram 12345678901.\nTelefon: 0532 123 45 67\nIBAN: TR330006100519786457841326", "TR-REQ-001"], |
| ] |
|
|
|
|
| with gr.Blocks(title="TSZ - Thyris Safe Zone Demo", theme=gr.themes.Soft()) as demo: |
|
|
| gr.Markdown(""" |
| # π‘οΈ TSZ (Thyris Safe Zone) |
| **Enterprise-grade PII detection and guardrails gateway** that prevents sensitive data from leaking to LLMs and third-party APIs. |
| |
| [](https://github.com/thyrisAI/safe-zone) |
| [](https://thyris.ai) |
| [](https://github.com/thyrisAI/safe-zone/blob/main/LICENSE) |
| """) |
|
|
| with gr.Tabs(): |
| with gr.TabItem("π Interactive Demo"): |
| with gr.Row(): |
| with gr.Column(): |
| rid_input = gr.Textbox( |
| label="Request ID (RID)", |
| placeholder="e.g., REQ-001 (auto-generated if empty)", |
| lines=1 |
| ) |
| input_text = gr.Textbox( |
| label="Input Text", |
| placeholder="Paste text containing emails, phone numbers, credit cards, API keys...", |
| lines=8 |
| ) |
| analyze_btn = gr.Button("Analyze & Redact", variant="primary") |
| gr.Markdown("### Try These Examples") |
| gr.Examples(examples=EXAMPLES, inputs=[input_text, rid_input]) |
|
|
| with gr.Column(): |
| rid_display = gr.Markdown(label="Request ID") |
| stats_output = gr.Markdown(label="Statistics") |
| redacted_output = gr.Textbox( |
| label="Redacted Output (Safe to send to LLMs)", |
| lines=8, |
| interactive=False |
| ) |
| detection_report = gr.Markdown(label="Detection Report") |
|
|
| gr.Markdown(""" |
| --- |
| ### Placeholder Format |
| |
| TSZ uses the format `[RID_TYPE_maskId]` for placeholders: |
| - **RID**: Request ID for audit correlation |
| - **TYPE**: Detection type (EMAIL, PHONE, etc.) |
| - **maskId**: Unique identifier derived from the original value |
| |
| Example: `[REQ-001_EMAIL_a1b2c3]` |
| |
| This allows you to: |
| - Track which request generated the redaction |
| - Identify the type of sensitive data |
| - Correlate with audit logs for compliance |
| """) |
|
|
| analyze_btn.click(fn=analyze_text, inputs=[input_text, rid_input], outputs=[redacted_output, detection_report, stats_output, rid_display]) |
| input_text.submit(fn=analyze_text, inputs=[input_text, rid_input], outputs=[redacted_output, detection_report, stats_output, rid_display]) |
|
|
| with gr.TabItem("π API Reference"): |
| gr.Markdown(""" |
| ## TSZ API Reference |
| TSZ is an enterprise-grade PII detection and guardrails gateway. It acts as a zero-trust middleware between your applications and external systems. |
| --- |
| ### Confidence & Guardrails Model |
| | Confidence | Action | |
| |------------|--------| |
| | < 0.30 | ALLOW (ignored) | |
| | 0.30 β 0.85 | MASK (redact) | |
| | β₯ 0.85 | AUTO-BLOCK | |
| --- |
| ### POST /detect |
| **Request:** |
| ```json |
| { |
| "text": "My email is user@company.com", |
| "rid": "REQ-001", |
| "guardrails": ["TOXIC_LANGUAGE"] |
| } |
| ``` |
| **Response:** |
| ```json |
| { |
| "redacted_text": "My email is [REQ-001_EMAIL_a1b2c3]", |
| "detections": [ |
| { |
| "type": "EMAIL", |
| "value": "user@company.com", |
| "placeholder": "[REQ-001_EMAIL_a1b2c3]", |
| "start": 12, |
| "end": 28, |
| "confidence_score": "0.78", |
| "confidence_explanation": { |
| "source": "HYBRID", |
| "regex_score": "0.55", |
| "ai_score": "0.90", |
| "final_score": "0.78" |
| } |
| } |
| ], |
| "blocked": false, |
| "contains_pii": true, |
| "overall_confidence": "0.78" |
| } |
| ``` |
| --- |
| ### Placeholder Format |
| TSZ generates unique placeholders in the format: |
| ``` |
| [{RID}_{TYPE}_{maskId}] |
| ``` |
| - **RID**: Request ID for audit log correlation |
| - **TYPE**: Detection type (EMAIL, CREDIT_CARD, etc.) |
| - **maskId**: Hash-based unique identifier |
| Example: `[RID-GW-001_EMAIL_a1b2c3]` |
| --- |
| ### POST /v1/chat/completions |
| OpenAI-compatible LLM Gateway with built-in guardrails. |
| **Headers:** |
| - `X-TSZ-RID`: Request ID for audit logs |
| - `X-TSZ-Guardrails`: Comma-separated validators |
| - `X-TSZ-Guardrails-Mode`: `final-only` | `stream-sync` | `stream-async` |
| **Response with tsz_meta:** |
| ```json |
| { |
| "id": "chatcmpl-58", |
| "choices": [...], |
| "tsz_meta": { |
| "rid": "RID-GW-001", |
| "guardrails": ["TOXIC_LANGUAGE"], |
| "input": [ |
| { |
| "redacted_text": "My email is [RID-GW-001_EMAIL_a1b2c3]", |
| "detections": [...], |
| "blocked": false |
| } |
| ], |
| "output": [...] |
| } |
| } |
| ``` |
| --- |
| ### Pattern Management |
| | Method | Endpoint | Description | |
| |--------|----------|-------------| |
| | `POST` | `/patterns` | Create pattern | |
| | `GET` | `/patterns` | List patterns | |
| | `DELETE` | `/patterns/{id}` | Delete pattern | |
| --- |
| ### Validators & Guardrails |
| ```json |
| { |
| "name": "TOXIC_LANGUAGE", |
| "type": "AI_PROMPT", |
| "rule": "Is this text toxic? Answer YES or NO.", |
| "expected_response": "NO" |
| } |
| ``` |
| --- |
| ### Health & Admin |
| | Endpoint | Description | |
| |----------|-------------| |
| | `GET /healthz` | Liveness probe | |
| | `GET /ready` | Readiness probe | |
| | `POST /admin/reload` | Clear caches | |
| --- |
| π **Full Documentation:** [API_REFERENCE.md](https://github.com/thyrisAI/safe-zone/blob/main/docs/API_REFERENCE.md) |
| """) |
|
|
| with gr.TabItem("π Quick Start"): |
| gr.Markdown(""" |
| ## Get Started with TSZ |
| ### Docker (Recommended) |
| ```bash |
| git clone https://github.com/thyrisAI/safe-zone.git |
| cd safe-zone |
| docker-compose up -d |
| ``` |
| TSZ will be available at `http://localhost:8080` |
| --- |
| ### Environment Variables |
| ```bash |
| # LLM Gateway Configuration |
| THYRIS_AI_MODEL_URL=https://api.openai.com/v1 |
| THYRIS_AI_API_KEY=sk-your-key |
| # Confidence Thresholds |
| CONFIDENCE_ALLOW_THRESHOLD=0.30 |
| CONFIDENCE_BLOCK_THRESHOLD=0.85 |
| # PII Mode |
| PII_MODE=MASK # or BLOCK |
| GATEWAY_BLOCK_MODE=BLOCK # BLOCK, MASK, or WARN |
| ``` |
| --- |
| ### Test with Request ID |
| ```bash |
| curl -X POST http://localhost:8080/detect \\ |
| -H "Content-Type: application/json" \\ |
| -d '{ |
| "text": "Email: test@example.com", |
| "rid": "MY-REQ-001" |
| }' |
| ``` |
| **Response:** |
| ```json |
| { |
| "redacted_text": "Email: [MY-REQ-001_EMAIL_abc123]", |
| "detections": [...], |
| "contains_pii": true |
| } |
| ``` |
| --- |
| ### Architecture |
| ``` |
| βββββββββββββββ βββββββββββββββ βββββββββββββββ |
| β Your App ββββββΆβ TSZ ββββββΆβ LLM API β |
| βββββββββββββββ βββββββββββββββ βββββββββββββββ |
| β β |
| β βββββββΌββββββ |
| β β Audit Log β |
| β β (with RID)β |
| βββββββββββββΆβββββββββββββ |
| ``` |
| --- |
| π **Documentation:** [github.com/thyrisAI/safe-zone/docs](https://github.com/thyrisAI/safe-zone/tree/main/docs) |
| """) |
|
|
| with gr.TabItem("βΉοΈ About"): |
| gr.Markdown(""" |
| ## About TSZ (Thyris Safe Zone) |
| Developed by **Thyris.AI** as an open-source enterprise AI security solution. |
| ### Key Features |
| | Feature | Description | |
| |---------|-------------| |
| | **Hybrid Detection** | Regex + AI-powered confidence scoring | |
| | **Traceable Redaction** | `[RID_TYPE_maskId]` format for audit | |
| | **LLM Gateway** | OpenAI-compatible proxy | |
| | **Streaming Support** | sync/async modes for SSE | |
| | **Guardrails** | AI validators (toxic, schema, custom) | |
| --- |
| ### Placeholder System |
| Each redacted value gets a unique, traceable placeholder: |
| ``` |
| [RID-GW-001_EMAIL_a1b2c3] |
| β β β |
| β β βββ Unique mask ID (hash of value) |
| β βββ Detection type |
| βββ Request ID for correlation |
| ``` |
| Benefits: |
| - Full audit trail |
| - SIEM integration |
| - Compliance reporting |
| - Debug & investigation |
| --- |
| ### Compliance Ready |
| - β
GDPR (EU) |
| - β
KVKK (Turkey) |
| - β
CCPA (California) |
| - β
HIPAA (Healthcare) |
| - β
PCI-DSS (Payment) |
| --- |
| Built with β€οΈ by [Thyris.AI](https://thyris.ai) |
| """) |
|
|
|
|
| if __name__ == "__main__": |
| demo.launch() |