| """ |
| Input sanitization / prompt-injection hygiene. |
| |
| The user's product description and target keywords are untrusted input. |
| Before they go anywhere near the LLM prompt, strip out patterns that look |
| like an attempt to override the system prompt. |
| |
| This is defense-in-depth, not a silver bullet: it reduces the surface for |
| naive injection attempts. It does not replace treating LLM output as |
| untrusted too (see llm/validation.py, which never trusts the model's |
| claims about its own compliance and re-checks everything in code). |
| """ |
| import re |
|
|
| |
| _INJECTION_PATTERNS = [ |
| r"ignore (all |any )?(previous|prior|above|the) (instructions?|prompts?)", |
| r"disregard (all |any )?(previous|prior|above|the) (instructions?|prompts?)", |
| r"forget (all |any )?(previous|prior|above|the) (instructions?|prompts?)", |
| r"you are now", |
| r"new system prompt", |
| r"system\s*:", |
| r"assistant\s*:", |
| r"\bact as\b", |
| r"reveal (your|the) (system )?prompt", |
| r"</?(system|assistant|user)>", |
| r"\[/?INST\]", |
| ] |
|
|
| _COMPILED = [re.compile(p, re.IGNORECASE) for p in _INJECTION_PATTERNS] |
|
|
| _REDACTION_MARKER = "[removed]" |
|
|
| |
| |
| MAX_INPUT_CHARS = 2000 |
|
|
|
|
| def sanitize_user_input(text: str | None) -> str: |
| """Strip likely prompt-injection attempts and cap length. |
| |
| Returns an empty string for None/blank input. |
| """ |
| if not text: |
| return "" |
|
|
| text = text.strip()[:MAX_INPUT_CHARS] |
|
|
| for pattern in _COMPILED: |
| text = pattern.sub(_REDACTION_MARKER, text) |
|
|
| |
| text = text.replace("```", "'''") |
|
|
| return text.strip() |
|
|
|
|
| def redact_for_logs(*_args, **_kwargs) -> str: |
| """Logs must never contain full user-submitted text or PII (email etc.). |
| |
| Anywhere we'd be tempted to log the raw product_description, email, or |
| LLM output, log this placeholder + a request id instead. Kept as an |
| explicit function (rather than just "don't log it") so call sites are |
| self-documenting about *why* the value is missing from logs. |
| """ |
| return "[redacted]" |
|
|