Spaces:
Sleeping
Sleeping
File size: 5,596 Bytes
70e66bb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | """
pattern_guard.py
A Python port of the PromptInjectionGuardrail originally contributed to
LangChain4j (https://github.com/langchain4j/langchain4j, module:
langchain4j-guardrails). Same detection taxonomy, same regex patterns,
ported here to demonstrate that the approach is framework- and
language-agnostic.
Categories covered (based on OWASP LLM01 - Prompt Injection):
1. Instruction override - "ignore previous instructions"
2. Role hijacking - "you are now a...", "act as a..."
3. Jailbreaks - "DAN", "developer mode", "bypass safety filters"
4. System prompt leakage - "reveal your prompt", "print your instructions"
5. Delimiter injection - ```system, <system>, [INST], <<SYS>>
6. Encoded injection - base64 payloads, "decode and execute"
This module has zero ML dependencies and zero external calls — every
check is a compiled regex match, which is why it runs in microseconds.
"""
import re
import time
from dataclasses import dataclass, field
from typing import List
@dataclass
class GuardResult:
"""Result of a single guardrail check."""
blocked: bool
reason: str
category: str
latency_ms: float
matched_pattern: str = ""
# ---------------------------------------------------------------------------
# Pattern catalog — mirrors PromptInjectionGuardrail.java exactly,
# with a category label attached to each pattern for richer UI display.
# ---------------------------------------------------------------------------
_PATTERNS: List[tuple] = [
# --- Instruction override ---
(re.compile(r"ignore\s+(all\s+)?(previous|prior|above|earlier|your)\s+(instructions?|prompts?|context|rules?|constraints?)", re.IGNORECASE),
"Instruction override"),
(re.compile(r"disregard\s+(all\s+)?(previous|prior|above|earlier|your)\s+(instructions?|prompts?|rules?)", re.IGNORECASE),
"Instruction override"),
(re.compile(r"forget\s+(everything|all|your\s+instructions?|what\s+you\s+(were|are)\s+told)", re.IGNORECASE),
"Instruction override"),
(re.compile(r"do\s+not\s+follow\s+(your\s+)?(instructions?|rules?|guidelines?|constraints?)", re.IGNORECASE),
"Instruction override"),
(re.compile(r"override\s+(your\s+)?(instructions?|programming|directives?|guidelines?)", re.IGNORECASE),
"Instruction override"),
# --- Role hijacking ---
(re.compile(r"you\s+are\s+now\s+(a|an|the)\s+", re.IGNORECASE),
"Role hijacking"),
(re.compile(r"act\s+as\s+(a|an|the)\s+(?!user|customer|person)", re.IGNORECASE),
"Role hijacking"),
(re.compile(r"pretend\s+(you\s+are|to\s+be)\s+(a|an|the)?\s+", re.IGNORECASE),
"Role hijacking"),
(re.compile(r"roleplay\s+as|play\s+the\s+role\s+of", re.IGNORECASE),
"Role hijacking"),
(re.compile(r"switch\s+(to|into)\s+(a\s+different\s+)?mode", re.IGNORECASE),
"Role hijacking"),
# --- Jailbreak phrases ---
(re.compile(r"\bDAN\b|do\s+anything\s+now", re.IGNORECASE),
"Jailbreak"),
(re.compile(r"developer\s+mode|jailbreak\s+mode|unrestricted\s+mode|god\s+mode", re.IGNORECASE),
"Jailbreak"),
(re.compile(r"your\s+true\s+(self|form|nature)|without\s+(any\s+)?(restrictions?|limitations?|filters?)", re.IGNORECASE),
"Jailbreak"),
(re.compile(r"bypass\s+(your\s+)?(safety|content|ethical)\s+(filter|check|guard|rule|restriction)", re.IGNORECASE),
"Jailbreak"),
# --- System prompt leakage ---
(re.compile(r"(reveal|show|print|output|display|repeat|tell\s+me)\s+(your\s+)?(system\s+)?(prompt|instructions?|context|rules?|configuration)", re.IGNORECASE),
"System prompt leakage"),
(re.compile(r"what\s+(is|are|were)\s+your\s+(original\s+)?(instructions?|rules?|system\s+prompt)", re.IGNORECASE),
"System prompt leakage"),
(re.compile(r"what\s+did\s+(they|your\s+creators?|the\s+developers?)\s+tell\s+you", re.IGNORECASE),
"System prompt leakage"),
# --- Delimiter injection ---
(re.compile(r"```\s*system|<\s*/?(system|prompt|instruction)\s*>", re.IGNORECASE),
"Delimiter injection"),
(re.compile(r"\[INST\]|\[/INST\]|<<SYS>>|<</SYS>>", re.IGNORECASE),
"Delimiter injection"),
# --- Encoded / obfuscated injection ---
(re.compile(r"base64\s*:\s*[A-Za-z0-9+/=]{20,}", re.IGNORECASE),
"Encoded injection"),
(re.compile(r"decode\s+(the\s+following|this)\s+and\s+(execute|run|follow)", re.IGNORECASE),
"Encoded injection"),
]
def check(text: str) -> GuardResult:
"""
Run the full pattern catalog against `text` and return a GuardResult.
Mirrors PromptInjectionGuardrail.validate() — first match wins,
returns immediately (fail-fast), exactly like the Java original.
"""
start = time.perf_counter()
if not text or not text.strip():
elapsed = (time.perf_counter() - start) * 1000
return GuardResult(
blocked=False,
reason="Empty input",
category="-",
latency_ms=elapsed,
)
for pattern, category in _PATTERNS:
match = pattern.search(text)
if match:
elapsed = (time.perf_counter() - start) * 1000
return GuardResult(
blocked=True,
reason=f"Prompt injection detected ({category})",
category=category,
latency_ms=elapsed,
matched_pattern=match.group(0),
)
elapsed = (time.perf_counter() - start) * 1000
return GuardResult(
blocked=False,
reason="No injection pattern matched",
category="-",
latency_ms=elapsed,
)
|