Symptom
Users of a Bedrock-backed assistant get your guardrail's blocked message ("Sorry, I can't help with that") for ordinary questions. Typical reports:
- A support agent asks how to "kill a stuck process" and gets blocked.
- A finance user asks about retirement planning and the guardrail refuses it as investment advice.
- Answers come back with
{NAME}or{EMAIL}placeholders where the user expected real values. - Long prompts that paste in logs or tickets are blocked, but short ones aren't.
The Converse response has "stopReason": "guardrail_intervened". That tells you a guardrail acted. It doesn't tell you which policy fired, or whether it blocked or masked. The trace tells you that.
Quick triage table
| What you see | Likely cause | Fix |
|---|---|---|
| Blocked on words like "kill", "attack", "exploit" in an IT context | Content filter VIOLENCE or MISCONDUCT set to HIGH | Lower the input strength for that category, re-test |
| Blocked on pasted logs, code or instructions | PROMPT_ATTACK filter flags pasted text as an injection attempt | Wrap only the user's question in guardContent, not the whole pasted document |
| Blocked on a topic close to a denied one | Denied topic definition too broad, or the examples overlap allowed use | Tighten the definition, add sample phrases that only match the denied use |
Values replaced with {EMAIL} etc. | Sensitive information filter set to anonymize (mask) | Change the action for that entity type, or accept the masking |
| Answer blocked after the model replied | Output assessment, often contextual grounding below threshold | Check contextualGroundingPolicy score against threshold |
| Exact term always blocked | Custom word filter or managed profanity list | Remove the word from the word policy |
Step 1: Get the trace from the failing call
Turn on the trace in the Converse call that is failing. In Python with boto3:
import boto3, json
brt = boto3.client("bedrock-runtime", region_name="us-east-1")
MODEL_ID = "<your-model-id>"
resp = brt.converse(
modelId=MODEL_ID, # the model ID or inference profile your app already uses
messages=[{"role": "user", "content": [{"text": "How do I kill a hung java process on Linux?"}]}],
guardrailConfig={
"guardrailIdentifier": "abc123xyz",
"guardrailVersion": "3",
"trace": "enabled",
},
)
print(resp["stopReason"])
print(json.dumps(resp.get("trace", {}).get("guardrail", {}), indent=2, default=str))
Look for inputAssessment (the prompt was blocked) or outputAssessments (the model's answer was blocked). Inside each assessment, find the entries with "action": "BLOCKED" or "ANONYMIZED":
contentPolicy.filters[]givestype(HATE,INSULTS,SEXUAL,VIOLENCE,MISCONDUCT,PROMPT_ATTACK),confidenceandaction.topicPolicy.topics[]gives the denied topicname.wordPolicy.customWords[]andmanagedWordLists[]give thematch.sensitiveInformationPolicy.piiEntities[]andregexes[]give the entity type and the matched text.contextualGroundingPolicy.filters[]givesscoreagainstthresholdforGROUNDINGandRELEVANCE.
Warning
The trace contains the matched text, including PII the guardrail masked. Don't send traces to general application logs. Enable trace only for debugging, or send it to a restricted log destination.
Step 2: Reproduce it with ApplyGuardrail
ApplyGuardrail evaluates text against a guardrail without calling a model. Use it to replay the exact user text quickly and cheaply:
aws bedrock-runtime apply-guardrail \
--guardrail-identifier abc123xyz \
--guardrail-version 3 \
--source INPUT \
--content '[{"text":{"text":"How do I kill a hung java process on Linux?"}}]' \
--output-scope FULL \
--region us-east-1 \
--query '{action:action,reason:actionReason,assessments:assessments}'
--source INPUTchecks text as a user prompt. UseOUTPUTto check a model answer.--output-scope FULLreturns entries that were evaluated but not detected, as well as the ones that fired. It helps you see how close a prompt is to the threshold. Per the CLI reference, full scope doesn't cover word filters or regex sensitive-information filters.actionisGUARDRAIL_INTERVENEDorNONE.
To see the configured strengths you are comparing against:
aws bedrock get-guardrail \
--guardrail-identifier abc123xyz \
--guardrail-version 3 \
--query '{status:status,content:contentPolicy.filters,topics:topicPolicy.topics[].name}'
Content filter strengths are NONE, LOW, MEDIUM and HIGH, set separately for input and output. A filter blocks when the detected confidence reaches the configured strength. HIGH strength blocks even low-confidence detections, so it is the most likely cause of false positives.
Step 3: Fix the cause
Content filters too strict
If VIOLENCE or MISCONDUCT fires on IT vocabulary at LOW confidence, lower the input strength for that category from HIGH to MEDIUM. Leave the output strength as it is unless output answers are also blocked. Change one category at a time.
Prompt attack filter catching pasted content
The PROMPT_ATTACK filter is designed to catch jailbreaks and instruction injection. Pasted runbooks, tickets and logs often contain phrases like "ignore the previous error" or "you must run", which look like injection. Don't switch the filter off. Instead, tell the guardrail which part of the message is the user's own input. Once a message contains a guardContent block, the guardrail evaluates only the content inside guardContent blocks:
messages = [{
"role": "user",
"content": [
{"guardContent": {"text": {"text": user_question}}}, # evaluated
{"text": f"Reference material:\n{retrieved_docs}"}, # not evaluated
],
}]
Caution
Content outside
guardContentis not checked at all. If the retrieved or pasted text comes from untrusted sources, you have removed the check that protects against indirect prompt injection. Scan untrusted documents separately withapply-guardrail --source INPUTat ingestion time, or keep them insideguardContentand accept some false positives.
The system prompt is not evaluated unless you put it in a guardContent block. Tool definitions, tool call arguments and tool results are not evaluated by Converse guardrails either. Check tool output yourself if it matters.
Denied topic too broad
A topic like "Investment advice" with the definition "Any discussion of money, savings or finance" blocks expense policy questions too. Write the definition around the behaviour you want to stop ("Recommendations to buy, sell or hold specific securities or funds") and give sample phrases that only match that behaviour. Test both a denied and an allowed phrase with apply-guardrail after every change.
Sensitive information masked unexpectedly
ANONYMIZED in the trace means the guardrail masked the value rather than blocking it. stopReason is still guardrail_intervened, so apps that treat every intervention as a refusal show the blocked message when the answer was only masked. Check the action field in the trace, not only stopReason.
Step 4: Ship the change safely
Guardrail edits go to the DRAFT version. Production should call a numbered version, so a change only takes effect when you publish and switch to it.
- Edit the guardrail in the console, or with
aws bedrock update-guardrail. The update call takes the full policy set, so start from the currentget-guardrailoutput and change only what you need. - Re-run your test set against
DRAFT:
while IFS= read -r line; do
result=$(aws bedrock-runtime apply-guardrail \
--guardrail-identifier abc123xyz --guardrail-version DRAFT \
--source INPUT \
--content "$(jq -cn --arg t "$line" '[{text:{text:$t}}]')" \
--query action --output text)
printf '%s\t%s\n' "$result" "$line"
done < guardrail-tests.txt
Keep two lists in guardrail-tests.txt (or two files): prompts that must pass and prompts that must be blocked. A fix that unblocks "kill a process" but also unblocks a real jailbreak is not a fix.
- Publish a new version and point the app at it:
aws bedrock create-guardrail-version \
--guardrail-identifier abc123xyz \
--description "Lower VIOLENCE input strength to MEDIUM, ticket 4821"
- Update the app config to the returned version number. Rolling back means pointing the app at the previous version number, because published versions don't change.
Tip
Keep the guardrail version in app configuration (Parameter Store, AppConfig, environment variable), not in code. You can roll back without redeploying.
Prevent it next time
- Keep a regression set of real allowed and blocked prompts per guardrail, and run it before every
create-guardrail-version. - Start content filters at
MEDIUMfor internal tools and raise individual categories only when you have evidence. - Use
guardContentdeliberately: decide for each message part (user input, system prompt, retrieved documents) whether it must be evaluated, and write that down. - Count interventions by policy type. Send trace data for blocked calls to a restricted log group and alert on a sudden rise in one category after a guardrail or prompt change.
- Handle
ANONYMIZEDandBLOCKEDseparately in the app so masked answers aren't shown as refusals. - Give users a "this was blocked wrongly" button that records the request ID, so false positives reach you as tickets instead of complaints.