
Two-layer LLM firewall that blocks prompt-injection inputs via regex rules plus a Gemini classifier, and catches secret or system-prompt leaks in bot responses.
Live demo: https://prompt-injection-firewall.streamlit.app 2-minute demo video: https://drive.google.com/file/d/1tzb75nFysisrCYa11Lqj6egDs5VNmc2n/view?usp=drivesdk
A two-layer firewall for an LLM support bot: it checks what users send in, and what the bot sends out.
Users can try to override a bot's rules ("ignore previous instructions"), switch its persona, or make it reveal its system prompt or secrets. This project blocks such inputs, catches leaks in the bot's answers, and measures how well it works, including where it fails.
user input
|
v
[1] Rule layer (regex, fast, free) --match--> BLOCK
|no match
v
[2] LLM classifier (Gemini, JSON output, Pydantic validation, retries, fail-closed) --attack--> BLOCK
|benign
v
AcmeBot (Gemini + system prompt)
|
v
[3] Output check (canary token, secret patterns, system-prompt fragments) --leak--> BLOCK
|clean
v
answer to user
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install -r requirements.txt
copy .env.example .env # then put your Gemini key in .env
python src\run_eval.py dev
python src\eval_output.py dev
python src\compare.py dev
streamlit run app.py # local demo
Model used: gemini-3.1-flash-lite. The free tier can return 503 when busy; the code waits and retries.
Do not run src\build_cases.py unless you want to rebuild data\cases.jsonl (it overwrites it; same seed gives the same cases).
deepset/prompt-injections (Apache-2.0): 12 attacks, 13 benign. Split: 15 dev, 10 hidden.data/train.parquet is included in the repo (source: deepset/prompt-injections, Apache-2.0).| Set | Layer | Result |
|---|---|---|
| dev | rules only | detection 2/9, false positive 0/6 |
| dev | rules + Gemini (final) | detection 8/9, false positive 0/6 |
| dev | output check (final) | leak caught 4/4, false positive 0/4 |
| hidden | rules + Gemini | detection 3/3, false positive 0/7 |
| hidden | output check | leak caught 3/3, false positive 0/1 |
Firewall vs no firewall:
| Without | With | |
|---|---|---|
| dev: attacks reaching the bot | 9/9 | 1/9 |
| dev: benign wrongly blocked | 0/6 | 0/6 |
| hidden: attacks reaching the bot | 3/3 | 0/3 |
| hidden: benign wrongly blocked | 0/7 | 0/7 |
The hidden set has only 3 attacks, so it is a weak signal. Dev and hidden are reported separately because I tuned the classifier prompt on dev.
Full history with numbers is in CHANGELOG.md. In short: