Guardrails and content safety
Export PDFEvery model call in Foundry passes through guardrails that score the input and output for harmful content and attacks. Understanding them matters twice: you must configure them to fit your use case, and your code must handle the moment they block something. This lesson explains every guardrail risk, severity levels, how to create a custom policy, how blocked requests look in code, and how to use Azure AI Content Safety directly.
Guardrails in one picture
A policy of classifiers that run on the user input and the model output of every request to a deployment or agent. Each classifier detects a risk; if a detection meets the configured threshold, the request or completion is blocked or annotated.
prompt ──► [input guardrail] hate · sexual · violence · self-harm · jailbreak · blocklists
│ blocked? → HTTP 400 (code: content_filter)
▼
model / agent ──► tool calls ──► [indirect attack check on documents/tool output]
│
▼
[output guardrail] hate · sexual · violence · self-harm · protected material · blocklists
│ blocked? → finish/incomplete reason: content_filter
▼
response + content-filter annotations
The risks a guardrail can detect
| Risk | Applies to | What it detects |
|---|---|---|
| Hate and fairness | Input + output | Attacks or discrimination based on identity (race, gender, religion, disability, and so on) |
| Sexual | Input + output | Sexual content, from suggestive to explicit |
| Violence | Input + output | Descriptions, threats, or glorification of violence and weapons |
| Self-harm | Input + output | Content about hurting oneself, including instructions or encouragement |
| Jailbreak (user prompt attacks) | Input | Attempts to make the model ignore its rules (“pretend you have no restrictions…”) |
| Indirect attacks (XPIA) | Documents and tool results | Instructions hidden inside retrieved documents, emails, or web pages |
| Protected material — text | Output | Known copyrighted text such as song lyrics, articles, and recipes |
| Protected material — code | Output | Code matching public repositories, with citation information |
| Groundedness (detection) | Output | Claims not supported by the provided sources |
| PII | Output | Personal data such as emails, phone numbers, and IDs |
| Custom blocklists | Input + output | Your own banned words or patterns (competitor names, internal codenames) |
Severity levels
The four harm categories return a severity: safe, low, medium, or high (0, 2, 4, 6 in the Content Safety API). You choose the threshold at which each category is blocked:
| Block threshold | Blocks | Effect |
|---|---|---|
| Low | Low, medium, and high | Strictest; may block legitimate content such as history or medical text |
| Medium (default) | Medium and high | Balanced default for most apps |
| High | High only | Permissive; for specialised apps that need sensitive content |
| Off / annotate only | Nothing (scores still returned) | Requires Microsoft approval for modified content filtering |
This offline simulation shows how thresholds change decisions for the same scores:
SEVERITY = {0: "safe", 2: "low", 4: "medium", 6: "high"}
LEVEL = {"low": 2, "medium": 4, "high": 6}
def decide(scores: dict[str, int], block_at: dict[str, str]) -> tuple[str, list[str]]:
"""Block when any category's severity reaches its configured threshold."""
hits = [f"{cat}={SEVERITY[sev]}" for cat, sev in scores.items()
if sev >= LEVEL[block_at[cat]]]
return ("blocked" if hits else "allowed"), hits
default_policy = {"hate": "medium", "sexual": "medium", "violence": "medium", "self_harm": "medium"}
strict_policy = {**default_policy, "violence": "low"}
samples = {
"recipe question": {"hate": 0, "sexual": 0, "violence": 0, "self_harm": 0},
"war history essay": {"hate": 0, "sexual": 0, "violence": 2, "self_harm": 0},
"graphic threat": {"hate": 2, "sexual": 0, "violence": 6, "self_harm": 0},
}
for name, scores in samples.items():
d1, _ = decide(scores, default_policy)
d2, hits = decide(scores, strict_policy)
print(f"{name:<18} default={d1:<8} strict={d2:<8} {hits}")
A history essay mentioning battles scores low for violence. The default policy allows it; a strict policy built for a children's app blocks it. There is no universally right setting — it depends on your users.
Create and assign a custom guardrail
- In the portal, open Guardrails + controls and create a new guardrail policy.
- For each harm category, set the input and output thresholds.
- Turn on Prompt Shields for jailbreak and indirect attacks, and choose whether to block or annotate.
- Enable protected material detection for text and code if you generate long-form content or code.
- Add blocklists for terms specific to your business.
- Assign the policy to deployments and agents. Every request to them now uses it.
Turning filters fully off, or annotate-only for harm categories, requires an approved application for modified content filtering. Managed customers can request it for legitimate use cases such as content moderation tools or medical research.
Handling blocked requests in code
A guardrail block is not a crash; it is a normal outcome your app must handle politely.
import openai
FRIENDLY = "Sorry, I can't help with that request. Please rephrase or contact support."
def safe_ask(client, deployment: str, user_text: str) -> str:
try:
response = client.responses.create(model=deployment, input=user_text)
except openai.BadRequestError as err:
if getattr(err, "code", None) == "content_filter": # the PROMPT was blocked
log_safety_event("input_blocked", err)
return FRIENDLY
raise
if response.status == "incomplete" and response.incomplete_details.reason == "content_filter":
log_safety_event("output_blocked", response.id) # the COMPLETION was blocked
return FRIENDLY
return response.output_text
With Chat Completions, the equivalent output signal is finish_reason == "content_filter", and the response carries content_filter_results with per-category severities. Log blocks (without storing harmful text unnecessarily) so you can tune thresholds and spot abuse.
Prompt Shields and indirect attacks
An attack where text the model reads contains instructions that override the developer's instructions. Direct injection comes from the user (“ignore previous instructions…”). Indirect injection hides inside content the app retrieves — a web page, a PDF, an email — and is especially dangerous for agents with tools.
Prompt Shields detect both. Layer them with design defences:
- Keep tool permissions minimal and require approval for actions with side effects.
- Clearly delimit retrieved content in prompts and tell the model it is data, not instructions.
- Never put secrets in the system prompt — assume a determined user can extract it.
- Validate tool arguments in code regardless of what the model says.
Azure AI Content Safety API
The same classifiers are available as a standalone service. Use it to moderate user-generated content that never reaches a model, to check text before storing it, or to add an extra check in a pipeline.
This example calls your Azure resources, so it needs a deployed project and a signed-in identity. Output will differ on every run.
from azure.ai.contentsafety import ContentSafetyClient
from azure.ai.contentsafety.models import AnalyzeTextOptions
from azure.identity import DefaultAzureCredential
safety = ContentSafetyClient(
endpoint=f"https://{resource_name}.cognitiveservices.azure.com",
credential=DefaultAzureCredential(), # needs Cognitive Services User
)
result = safety.analyze_text(AnalyzeTextOptions(text="I will hurt you if you do that again"))
for item in result.categories_analysis:
print(f"{item.category:<10} severity={item.severity}")
Responsible AI beyond filters
Guardrails are one layer. Microsoft's responsible AI practice — and most regulations, such as the EU AI Act — expect a full process:
| Stage | What you do |
|---|---|
| Map | List the harms your app could cause and who could be affected; red-team it. |
| Measure | Evaluate safety and quality systematically (previous lesson). |
| Mitigate | Layers: model choice, guardrails, instructions, grounding, tool limits, UX (disclosures, feedback buttons). |
| Operate | Monitor production, review incidents, re-evaluate after changes, keep documentation. |
- Add a
children_policyto the simulation that blocks every category at low, and test it on all three samples. - Send a jailbreak-style prompt to your deployment and inspect the error body.
- Create a blocklist containing a fake codename and confirm a prompt containing it is blocked.
- Guardrails score both inputs and outputs of every request to a deployment or agent.
- Harm categories (hate, sexual, violence, self-harm) have severities; the default blocks medium and above.
- Prompt Shields catch jailbreaks and indirect attacks; protected material and groundedness detection protect outputs.
- Handle blocks gracefully:
400 content_filterfor prompts,content_filterfinish or incomplete reason for outputs. - Guardrails are one layer — combine with least-privilege tools, grounding, evaluation, and monitoring.