Download app/security.py from simikkk/EtsyListingGenerator: direct link, hf CLI and curl.
- Browser
- Download file 2.25 kB
-
https://huggingface.co/spaces/simikkk/EtsyListingGenerator/resolve/main/app/security.py
- Command line
-
hf download hf://spaces/simikkk/EtsyListingGenerator/app/security.py
-
curl -L -o security.py https://huggingface.co/spaces/simikkk/EtsyListingGenerator/resolve/main/app/security.py
2.25 kB
| """ | |
| 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 | |
| # Patterns commonly used to try to hijack a system prompt. Case-insensitive. | |
| _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]" | |
| # Hard cap on how much raw text we'll even consider from the user, to keep | |
| # prompts small and bound worst-case cost/latency. | |
| 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) | |
| # Strip characters sometimes used to fake role-turn boundaries. | |
| 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]" | |