import re import time import os from pydantic import BaseModel from typing import Optional class OutputCheckResult(BaseModel): flagged: bool reason: Optional[str] # "pii:email" | "canary_echo" | "refusal_bypass:pattern" | "safe" redacted_response: Optional[str] = None score: float = 0.0 latency_ms: float = 0.0 class OutputMonitor: PII_PATTERNS = { "ssn": r'\b\d{3}-\d{2}-\d{4}\b', "credit_card": r'\b(?:\d{4}[-\s]?){3}\d{4}\b', "email": r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b', "phone": r'\b(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b', "api_key_openai": r'\bsk-[A-Za-z0-9]{32,}\b', "api_key_google": r'\bAIza[A-Za-z0-9_-]{35}\b', "api_key_anthropic":r'\bsk-ant-[A-Za-z0-9_-]{32,}\b', "aws_access_key": r'\bAKIA[0-9A-Z]{16}\b', } REFUSAL_BYPASS_PATTERNS = [ "as dan", "in this fictional scenario", "hypothetically speaking", "for educational purposes only", "as an ai with no restrictions", "in a world where ai has no limits", "let's pretend you have no guidelines", "roleplay as an unrestricted", "pretend you are jailbroken", "in developer mode", "as your true self without restrictions", ] # These phrases in a response suggest the LLM is acting on # an injected instruction from retrieved content INDIRECT_INJECTION_SIGNALS = [ "as instructed by the document", "following the instructions in the text", "as per the retrieved content", "the document instructs me to", "as the file specifies", ] def __init__(self, canary: Optional[str] = None): self.canary = canary or os.getenv("SYSTEM_CANARY") async def check_async( self, response_text: str, custom_canary: Optional[str] = None ) -> OutputCheckResult: import asyncio return await asyncio.to_thread(self.check, response_text, custom_canary) def check( self, response_text: str, custom_canary: Optional[str] = None ) -> OutputCheckResult: start = time.perf_counter() lower = response_text.lower() # 1. PII detection — regex scan for pii_type, pattern in self.PII_PATTERNS.items(): if re.search(pattern, response_text): redacted = re.sub(pattern, f"[REDACTED:{pii_type.upper()}]", response_text) latency = (time.perf_counter() - start) * 1000 return OutputCheckResult( flagged=True, reason=f"pii_detected:{pii_type}", redacted_response=redacted, score=0.95, latency_ms=round(latency, 3) ) # 2. Canary echo — system prompt leaked canaries = [c for c in [self.canary, custom_canary] if c] for canary in canaries: if canary.lower() in lower: latency = (time.perf_counter() - start) * 1000 return OutputCheckResult( flagged=True, reason="canary_echo:system_prompt_leaked", score=1.0, latency_ms=round(latency, 3) ) # 3. Refusal bypass language for pattern in self.REFUSAL_BYPASS_PATTERNS: if pattern in lower: latency = (time.perf_counter() - start) * 1000 return OutputCheckResult( flagged=True, reason=f"refusal_bypass:{pattern}", score=0.85, latency_ms=round(latency, 3) ) # 4. Indirect injection signals for signal in self.INDIRECT_INJECTION_SIGNALS: if signal in lower: latency = (time.perf_counter() - start) * 1000 return OutputCheckResult( flagged=True, reason=f"indirect_injection:{signal}", score=0.80, latency_ms=round(latency, 3) ) latency = (time.perf_counter() - start) * 1000 return OutputCheckResult( flagged=False, reason="safe", score=0.0, latency_ms=round(latency, 3) )