Part 6 left the assistant answering real questions well. The obvious next step was the chat widget itself, but I held off. The /ask endpoint has been live since part 5 with no protection at all, and I wanted it secured before exposing it to the public through an actual widget, not the other way around.
Two went in together: a per-IP rate limiter, twenty requests per ten minutes, and a Turnstile check on every request. The odd part is that nothing can solve a Turnstile challenge yet, since the widget doesn't exist, so the endpoint now rejects everything with a 403. I chose that order anyway: it was already live and already costing money with zero bot protection, which felt like the bigger risk.
Conversation logging came third, off by default, forced on when a question looks like abuse or a prompt injection attempt. That check started English-only, which missed Dutch for no good reason, so Dutch went in too. But no keyword list, in any number of languages, stops someone determined to dodge one, so this stays a cheap signal for the obvious case, not real security. The actual defense against injection is the generation prompt's own instruction to ignore anything trying to override it, which the model judges by meaning, not wording, so it holds regardless of language.
That review also caught an older bug: the prompt said to answer in the visitor's language, English or Dutch, but never said what to do with a third one. English and Dutch stay the intended scope. A third language now gets a clear, honest redirect instead of a silent guess.
I tested what I could. Twenty-three requests at the live endpoint matched the code exactly: twenty 403s, then 429s. Everything past Turnstile is written and reviewed, but untested until the widget exists.
Left: cache invalidation, and an actual box for a visitor to type into.