Guardrails are the controls that sit around a model, as separate components inspecting what went in and what came out, where training changes what the model itself tends to do. The idea comes straight from web application security, where a firewall in front of an application and output encoding behind it have been standard for twenty years, and the AI specific products arrived in 2023 when the model developers and the cloud platforms shipped classifiers built for the purpose. The Practitioner course cares because a guardrail can be changed without retraining anything, and because the half of the subject that concerns what the application does with the response is the half practitioners most often get wrong.
The sections below take the controls on the way in, the controls on the way out, improper output handling, why layers never become a boundary, where the managed services stop, and end with a fifteen minute trace of one response.
The controls on the way in
Three controls sit between the user and the model.
Request classification decides what kind of request this is before the model sees it, meaning support question, code request, medical advice or something outside the product. Routing out of scope requests to a fixed response is cheaper and more reliable than a paragraph of prompt asking the model to stay on topic.
Topic and jailbreak detection looks for categories the team has decided not to serve and shapes that try to override the instructions, meaning roleplay framings, encoded text and instructions buried in a pasted document. It is pattern matching against somebody who can rewrite their attempt, so it reduces volume and closes no door.
Rate limiting belongs here too and gets forgotten, because it has nothing to do with content. It bounds how many attempts one caller gets, which makes an attack needing a hundred rephrasings not worth mounting.
The controls on the way out
Whatever got through on the way in, four controls inspect what comes back.
Content filtering applies the same categories in reverse, assuming something got through.
Schema validation checks the response is the shape the code expects. If the request asked for an object with three fields, the response is parsed, the fields are confirmed to exist and to hold the types assumed, and anything else is rejected.
Grounding checks compare the response against the sources it was meant to use. Where every claim should trace to a retrieved passage, verifying that citations exist and contain what was attributed to them catches confident invention no content filter can see.
Refusal of malformed responses is the step teams skip. What happens when validation fails is part of the design, whether that means one retry, a fixed fallback or escalation to a person. A system with nothing decided here has undefined behaviour, which is a different thing from having none.
Improper output handling as the failure practitioners get wrong
The controls on the way out lead to the failure practitioners get wrong most often, which OWASP calls improper output handling. It happens when model output reaches something that executes it, meaning a template that renders it, a parser, a database driver, a shell, a browser or a tool call.
Model output is treated as untrusted user input, because that is what it is. It was produced from text including a user's request and probably a retrieved document, so anybody who can influence either can influence the output. A recurring class of finding since 2023 has been a chat interface rendering a markdown image whose address carried the conversation's contents to an attacker's server, and the fix in every case was in the application, which stopped rendering images from arbitrary domains.
In practice that means escaping before rendering, so markup in a response is displayed and never executed. It means validating before parsing, never concatenating a response into a query, a shell command, a file path or a request URL, and using parameterised calls for all of them. And it means constraining the model to a schema and checking the parsed values against an allowed set, since free text that looked sensible in testing is the thing that reaches production untested. The category fell from fifth to tenth on the 2026 OWASP list because teams largely learned to do this, and the fall says the control became routine, with the risk unchanged for any team that skipped it.
Why layers never become a boundary
All of the controls above are probabilistic. Each has a threshold somebody tuned, and tightening it trades false negatives for false positives on real users. Layering helps, since an attack has to survive every layer, and it still does not turn a probability into a boundary.
The boundary is permissions. What the system can do when a guardrail fails is decided by the credentials the code runs with, the scope of the tools an agent may call, and whether its account can write as well as read. A model talked into requesting a deletion deletes nothing if its token is read only. The design target is that the worst case of a guardrail failure is a bad answer, with a bad action out of reach.
Where the managed services stop
Managed guardrail services exist on every platform, including Guardrails for Amazon Bedrock, Azure AI Content Safety, and Model Armor alongside the safety filters on Google Cloud, with open source options such as Llama Guard and NeMo Guardrails running wherever the team hosts. All of them do the classification. None does the escaping, the schema checks or the permission scoping, which live in the application, and a team that bought a guardrail product and stopped there has bought the first half of the subject.
Practise this
The exercise needs one real response from a feature the team runs, fifteen minutes, and Python for the second half.
Take that response and trace it. Write down every place the application passes it onward unchecked, including rendering it in a page, writing it to a log, using a field as a key, and handing it to a tool. Then run this and watch a polite response fail three checks.
import html, json, re
raw = '{"answer": "<b onmouseover=x>hello</b>", "ticket_id": "42 or 1=1"}'
ALLOWED = {"answer", "ticket_id"}
try:
data = json.loads(raw)
except json.JSONDecodeError:
raise SystemExit("malformed, refuse")
if set(data) != ALLOWED:
raise SystemExit("unexpected fields, refuse")
if not re.fullmatch(r"[0-9]{1,10}", data["ticket_id"]):
raise SystemExit("ticket_id is not an id, refuse")
print(html.escape(data["answer"]))
The payload is inert and the response is inoffensive, so no content filter would have objected. Every check that fires is about shape and destination, with tone playing no part, and the one that matters on the team's own list is wherever a response reaches something that runs, queries or renders.
It teaches that the useful question is never whether the output was offensive. The useful question is what the code does with it next.