
five security controls every AI app needs
everyone's shipping ai, almost nobody's locking it down
every week there's a new "we added ai to our app" launch post. cool. also, half of these things are one weird prompt away from leaking a system prompt, draining an api budget, or spitting out unsanitized garbage straight into a database query. llm powered features get shipped like normal software, but they don't behave like normal software. the input is unstructured human language and the output is whatever the model feels like generating that day. that combo needs its own baseline. here are the five controls that matter, and why each one exists.
1. validate and length-limit user input
an llm will happily accept a 50,000 character wall of text designed to bury instructions, confuse context, or just cost you a fortune in tokens. treat prompt input like any other untrusted input field: cap the length, strip or flag weird unicode tricks, and reject stuff that doesn't match the expected shape of a real request.
this isn't about being paranoid for no reason. it's the same logic as limiting a text field on a signup form, except the "field" now feeds directly into a model that makes decisions or generates content on your behalf. if you don't bound it, someone else will bound it for you, and not in a way you'll like.
2. never put secrets in the prompt
this one keeps happening and it's an easy fix. api keys, internal urls, database credentials, customer data, none of that belongs in a system prompt or a context window. prompts get logged, cached, sometimes echoed back to the user through clever extraction tricks. if a secret is in the prompt, assume it can eventually end up in the output.
keep secrets where they've always belonged, in environment variables or a secrets manager, referenced by your backend code, never passed as text the model can see or repeat.
# bad: secret baked into the prompt string
prompt = f"you are a support bot. api_key={API_KEY}. answer the user."
# better: secret stays server-side, never touches the model
response = call_llm(system_prompt, user_input)
# api calls using API_KEY happen separately, outside the prompt context
3. sanitize model output before it touches anything real
the model's response is not trusted content just because it came from "your" ai. if that output gets rendered as html, inserted into a sql query, or piped into a shell command, you've built an injection pipeline with extra steps. sanitize and encode output the same way you'd handle any user-generated content: escape html, use parameterized queries, never eval or execute raw model text.
a good mental model: the llm is basically another untrusted user sitting between your real user and your backend. treat its output with the same suspicion.
4. rate-limit and cost-cap per user
llm apis charge per token, and a single motivated user can loop a request thousands of times in an hour if nothing stops them. that's not a security breach in the classic sense, it's a billing disaster with your name on it. set per-user rate limits, set a hard cost ceiling per account or per day, and alert when usage spikes way above normal.
this also doubles as abuse control. bots love free ai endpoints. a rate limit is the fence around your wallet and your infrastructure at the same time.
5. log prompts to catch jailbreak attempts
you can't defend what you can't see. log the prompts coming in and the outputs going out, and actually review that data for patterns: repeated attempts to override instructions, weird role-play framing, "ignore previous instructions" style phrasing. these are the fingerprints of someone probing your system for a way around its guardrails.
logging also gives you a paper trail if something does go wrong, so you can figure out exactly what input triggered a bad output instead of guessing.
# minimal logging pattern
log.info({
"user_id": user_id,
"prompt": user_input,
"output": model_response,
"timestamp": now()
})
the takeaway
none of this requires a phd in ml security. it's the same defensive mindset you already apply to normal web apps: don't trust input, don't leak secrets, don't trust output either, rate limit everything, and log what happens. the difference is the attack surface moved from forms and apis to plain english sentences, which is exactly why it gets skipped. audit your own ai feature against these five controls this week. if even one is missing, that's your next fix, not someday, now.