The situation
A retail SaaS company has been running a customer-service chatbot on Bedrock for four months. Claude Sonnet behind a thin application layer, a knowledge base for returns and shipping policy, a handful of tools for order lookup. Red-team exercises before launch covered the obvious harms. CSAM, hate speech, weapon instructions, and the bot passed.
Three incident classes have surfaced since.
Leaking personal data. A user uploads a scan of a document containing a social security number and asks the bot to read the name off it. The reply acknowledges the upload and repeats the SSN back in full. A separate case echoes a pasted credit-card number from a complaint. Neither prompt is malicious. The model answers the question it was asked, from the text it was handed.
Talking about competitors. “How does your billing compare to Acme?” gets two paragraphs of side-by-side feature comparison, politely framed, factually wobbly, and the sort of thing legal and marketing will each independently ask to stop. Another user asks for third-party tools that integrate with the product; the reply lists three named competitors.
Wrong about policy. A subscriber asks when their refund will arrive. The bot quotes a fourteen-day window. The policy in the knowledge base is thirty days. Fourteen appears in none of the retrieved passages.
What actually matters
The three incidents look like three different problems and are the same problem three times over: the model produced something the business doesn’t want, and a prompt instruction didn’t stop it. That means the System promptThe instruction block that frames the model’s behaviour for a session, separate from the user’s messages. is not a safety boundary. A system prompt saying “never reveal PII, never discuss competitors, only answer from retrieved documents” holds most of the time, and the gap between most of the time and always is where the production incidents live. Any design that relies on carefully worded prompts as the enforcement layer has a guaranteed failure mode; what changes between products is only the rate.
The filter jobs also differ in what enforcement means. PII redaction is a pattern-match problem: a definition of a social security number that holds up, a definition of a card number that holds up, and a detector that either finds them or doesn’t. Topic bans are semantic, because “competitor products” isn’t a keyword but a cluster of phrasings no keyword list ever catches all of. GroundingConstraining a model to answer from provided sources rather than from whatever it absorbed during training. is a comparison, does this claim match this passage, and it needs the retrieved context inside the check. Content moderation is a fourth shape again. Lumping them into one Lambda means writing four detectors badly.
Placement matters as much as detection. Input filtering catches the pasted SSN before the model reads it; output filtering catches the echoed SSN and the drifted policy number before the user reads it. The blast radius of either direction failing is the same, a regulator or a journalist reading the transcript, so filters run on both sides of the invocation. That puts the mechanism in the model call path rather than a Lambda someone has to remember to invoke, and the same path has to cover whichever surface the application uses. RAGA pattern where you retrieve relevant documents at query time and stuff them into the prompt so the model can ground its answer on them. already passes the retrieval context the grounding check needs, and orchestration above it coordinates tool calls.
Then ownership, visibility and cost. Legal needs a new competitor name on the ban list on a Friday afternoon, and if the policy lives in application code that is a release rather than a version bump. Every intervention has to come back with a structured reason, which category, which topic, which filter, so the team can alarm on spikes (a denied-topic rate doubling at 2am is either a JailbreakA prompt that bypasses a model’s safety training and gets it to produce output it would normally refuse. campaign or a misconfigured prompt) and tune thresholds against real traffic. And a third-party DLP scanner adds a network round-trip on every turn plus a contract to manage, where a managed in-call filter is metered per policy against the text it evaluates, so the charge lands on the existing AWS bill.
What we’ll filter on
Six distinct safety jobs on the same prompt.
- Broad content safety. Cover the harm dimensions red team already found, hate, insults, sexual, violence, misconduct, plus prompt-injection on the input side. Needs tuneable strength per category.
- Topic-level policy. Block conversation about topics the business doesn’t want the assistant covering, competitor products here, but the same shape fits legal advice or investment recommendations. The trigger is a topic expressed in many words, not a keyword.
- PII detection and redaction. Find SSNs, cards, bank accounts, addresses, names in both input and output. Bidirectional, input so pastes don’t reach the model, output so echoes and hallucinations don’t reach the user.
- Grounding in retrieved context. Verify the reply actually follows from the documents retrieved. Catch the thirty-days-becomes-fourteen case at the response boundary, not at complaint time.
- Compliance with rules that are already written down. Refund eligibility, cancellation windows, and the conditions Legal publishes are documented rules, and an answer either follows from them or contradicts them. That check wants a verdict, not a score.
- Operable by the team that ships the bot. No new long-running service to run, scale and patch. Policy changes are a console edit and a version bump, not a release.
The landscape
Four shapes for wrapping GuardrailA filter or rule applied to an LLM’s inputs or outputs to keep it inside safe, legal, or on-brand behaviour. around a Bedrock invocation.
Bedrock Guardrails. A managed policy surface that wraps calls through InvokeModel, InvokeModelWithResponseStream, Converse and ConverseStream, attaches to RetrieveAndGenerate, and attaches to the prompt and knowledge-base nodes of a Bedrock Flow. (Bedrock Agents is now Bedrock Agents Classic, in maintenance mode and closed to accounts with no prior usage since 30 July 2026, so new agent work goes to Bedrock AgentCore, where the guardrail still applies to the model invocation and an AgentCore Gateway policy covers the tool layer, for content filters, prompt attack and sensitive information.) A guardrail is a versioned configuration with up to six policy types: content filters across six categories (hate, insults, sexual, violence, misconduct, prompt attack), denied topics in natural language, sensitive information filters (31 built-in PII types plus custom regex, each set to BLOCK, ANONYMIZE or NONE), word filters (custom list plus managed profanity), a contextual grounding check returning grounding and relevance scores on outputs, and Automated Reasoning checks, which test an answer against a formal model extracted from a written policy. Content filters and denied topics are configured against a tier: standard adds broader language coverage, prompt-leakage detection, detection inside code, and 1,000-character topic definitions against classic’s 200, and requires cross-Region inference. Invoked by passing guardrailIdentifier and guardrailVersion on the model call. ApplyGuardrail runs the same policy on arbitrary text with no model call.
Custom moderation via Lambda plus Amazon Comprehend. A pre-processing Lambda calls Comprehend’s DetectPiiEntities (35 entity types, English and Spanish only) and DetectToxicContent (seven categories plus an overall score, English only), optionally calls another Bedrock model as a classifier for denied topics, scrubs or rejects, and forwards to the model. Comprehend’s prompt safety classifier is closed to new customers, so injection detection is a bespoke build. A post-processing Lambda mirrors the pass on output. The application owns the chaining, the errors, and every tuning knob.
Third-party DLP scanner. Route input and output through a commercial product (Nightfall, Private AI, or similar. Macie is S3 batch discovery, not in-band chat). Strong on PII; weaker on category harms and non-pattern denied topics; contextual grounding typically out of scope.
Prompt engineering alone. “Never discuss competitors, never reveal PII, only answer from the retrieved documents, refuse unsafe content.” Fast, free, and not enforcement. Every new jailbreak is a production incident; every creatively phrased request slips through.
Evaluation
Side by side
| Option | Content categories | PII redaction | Denied topics | Grounding check | Formal policy check | Low ops |
|---|---|---|---|---|---|---|
| Bedrock Guardrails | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Custom Lambda + Comprehend | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ |
| Third-party DLP | ✗ | ✓ | ✗ | ✗ | ✗ | ✗ |
| Prompt engineering alone | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ |
Prompt engineering ticks “low ops” because it’s zero infrastructure, but it fails every enforcement column, so low but unsafe. Bedrock Guardrails is the only row ticking every column cleanly.
How Guardrails wraps a Bedrock invocation
Three things the diagram flattens worth spelling out.
Prompt attack is input-only: the API takes an inputStrength and no output strength. It covers jailbreaks, prompt injection, and, on the standard tier, prompt leakage. There is no symmetric output check; the other output filters catch whatever an injection produced. One trap here: with InvokeModel and InvokeModelWithResponseStream the user’s text has to be wrapped in guardrail input tags, or prompt attacks are not evaluated at all, and a system prompt that reads like an injection is what the tags keep out of scope.
Contextual grounding is output-only. The check scores a generated reply against the retrieval context passed in, and on the input side there is no reply yet to score.
Automated Reasoning checks sit on the output side too, alongside grounding, and aren’t drawn. They take the question and the reply together, which the diagram’s left-to-right flow has no clean place for. They also run in detect mode only: the finding comes back on the response and the application acts on it.
The solution
Content filters are fixed; strength is configured per guardrail.
- Hate. Attacks on identity groups.
- Insults. Language demeaning an individual without the group-identity angle.
- Sexual. Direct or indirect references to body parts, physical traits, or sex.
- Violence. Glorification of, or threats of, physical harm to a person, group or thing.
- Misconduct. Illegal activity, fraud, criminal how-tos.
- Prompt attack. Input-only. Injection patterns trying to rewrite or extract the system prompt.
Each strength (NONE, LOW, MEDIUM, HIGH) applies independently to input and output for the first five. When a category trips, response metadata carries GUARDRAIL_INTERVENED and the category that caught it. Two things sit outside this configuration. Child sexual abuse material is not a filter strength at all: Bedrock runs its own automated abuse detection, and apparent CSAM in an image input returns a ValidationException before the guardrail is reached. And content filters evaluate user messages, system prompts and model text replies only, skipping tool results, tool definitions and model-generated tool-call arguments, so the order-lookup tool path is uncovered.
Denied topics, PII, and word filters
The competitor-comparison incident is not a content-filter failure, the replies were polite, not hateful. They were off-topic. That’s the denied-topics shape: a name, a natural-language definition (200 characters on the classic tier, 1,000 on standard), up to five sample phrases of 100 characters each, up to 30 topics per guardrail.
Name: Competitor products
Definition: Any discussion of products, services, pricing,
or features offered by companies other than our own
that compete in the same category.
Examples:
- "How does this compare to Acme?"
- "Is BrandX better than your product?"
- "Recommend alternatives to your service."
The runtime classifies each turn against these definitions. An input-side match blocks the question; an output-side match catches unprompted comparisons. A natural-language definition holds where a keyword list does not, because competitors get renamed, new ones appear, and users phrase comparisons without ever saying “compare.” Note that the definition describes a theme, not a list of names: AWS’s guidance is to keep entity names out of topic definitions and hand them to word filters.
Sensitive information filters cover 31 built-in PII types: US_SOCIAL_SECURITY_NUMBER, CREDIT_DEBIT_CARD_NUMBER, US_BANK_ACCOUNT_NUMBER, EMAIL, PHONE, ADDRESS, NAME, IP_ADDRESS, AWS_ACCESS_KEY, US_PASSPORT_NUMBER, DRIVER_ID, Canadian and UK health and insurance numbers, plus up to 30 named regex patterns of 500 characters each. Per-type action is BLOCK, ANONYMIZE or NONE, and inputAction and outputAction can differ, so a type can be masked on the way in and blocked on the way out. Regex here does not support lookaround.
Three sharp edges. NAME catches more than teams expect, and a bot greeting “{NAME}, I can help with that” because the user’s own name got masked is a poor experience, so disable it on the fields where a name belongs. The tool-use gap applies here as well: PII the model writes into tool-call arguments, PII in tool results the application returns, and PII in the tool definitions themselves are all unevaluated. And masking stops at the response. Model invocation logs keep the original unmasked request, and the match field in the guardrail trace carries the raw detected value by design, so protecting the logs is a separate job with CloudWatch Logs data protection.
Word filters are a managed profanity toggle plus up to 10,000 custom literal terms of 100 characters each, and they are the one policy AWS does not meter. Competitor brand names get both treatments, denied topic catches comparisons in general, word filter catches the slip where the model names a brand directly.
Contextual grounding
The thirty-days-becomes-fourteen incident isn’t content, PII, or topic. It’s grounding, the reply contained a claim the retrieved passage didn’t support. The check returns two confidence scores per output:
- Grounding. How well the claim is supported by the source passages.
- Relevance. How directly the claim addresses the user’s question.
Thresholds are set per guardrail between 0 and 0.99, and 1 is rejected because it would block everything; below-threshold responses trip GUARDRAIL_INTERVENED. The check needs three things: the grounding source, the query, and the reply to score. On Converse those are marked with grounding_source and query qualifiers on the guard content blocks; on the Invoke APIs with amazon-bedrock-guardrails-groundingSource_xyz and query_xyz tags; with ApplyGuardrail the reply goes in as a third content block. The caps are 100,000 characters of grounding source, 1,000 of query, 5,000 of response.
Two caveats to read before leaning on it. Content marked grounding_source or query is excluded from every other policy unless it also carries guard_content, so PII inside retrieved passages goes unfiltered by default. And AWS scopes the check to summarisation, paraphrasing and question answering, stating that conversational chatbot use is not supported, so on a multi-turn thread a low score is a signal to review rather than a verdict to block on. With knowledge bases it is also unsupported on Claude 3 Sonnet and Haiku.
Automated reasoning checks
Filtering with a detector or a score settles cases that turn on a match or a number: this text contains a card number, this claim scores 0.3 against the passage it cites. Refund eligibility is a different shape. The rule is written down, it has conditions, and an answer either follows from those conditions or contradicts them. Automated Reasoning checks are the policy type for that shape.
The input is a policy document in natural language, an eligibility rule, a refund rule, a regulatory obligation Legal has already drafted. Bedrock extracts a formal logical model from it: variables, their types, and the rules relating them, expressed in a subset of SMT-LIB. A fidelity report scores how well that extraction covers the source and how faithfully it represents it, statement by statement. The extraction is a first pass rather than the finished artefact. A human reads the model, writes test questions against it, and corrects the variables and rules wherever the extraction diverged from the prose. Accuracy comes out of that review loop, and the corrected model is versioned alongside the guardrail.
At runtime the check takes a question and its answer together and returns a finding, not a score.
VALID. The answer follows from the policy, with the supporting rules attached.INVALID. The answer contradicts the policy, and the finding carries the rules it broke.SATISFIABLE. The answer could be true or false depending on facts it never stated.IMPOSSIBLE. The premises contradict each other or the policy, so no consistent answer exists.TRANSLATION_AMBIGUOUS. The translation models disagreed on what the answer means.NO_TRANSLATIONS. Part of the input mapped onto no policy variable; it arrives alongside other findings.TOO_COMPLEX. The input or the policy exceeded what the solver could process.
Set that beside contextual grounding, because the two are easy to conflate. Grounding asks whether a passage supports the claim. Automated reasoning asks whether the claim follows from the rule. A reply that quotes the thirty-day window correctly and then tells a subscriber on day forty that they qualify passes grounding, because every sentence traces to the retrieved passage, and comes back INVALID from automated reasoning, because the conclusion contradicts the rule. Run both; they catch different failures.
Findings come back on Converse, InvokeModel and ApplyGuardrail. The check never blocks anything: it runs in detect mode only, so acting on a finding is application code.
The limits are worth knowing before configuring guardrails based on policy requirements. The mechanism suits a bounded rule set somebody has written down, and it does not cover open-ended factual accuracy, where no policy document exists to check against. Source documents cap at 5 MB and 50,000 characters, and a guardrail holds two policies, so a two-hundred-page handbook gets split down to the sections that answer subscriber questions rather than uploaded whole. It handles English (US) only, it does not work with the streaming APIs, and it is generally available in six Regions, none of them in Asia Pacific, so a Sydney workload calls a US or EU endpoint for it. TRANSLATION_AMBIGUOUS is a runtime outcome to design for rather than an error to log and forget: a hand-off or a hedged reply is better than serving an answer the translation step could not pin down.
Worked example
- One guardrail, versioned, standard tier on content filters and denied topics, all six policy types enabled.
- Content filters. MEDIUM on insults (a support bot gets rude users and needs to respond neutrally); HIGH on hate, sexual, violence, misconduct; HIGH on prompt attack.
- Denied topics. Competitor products, Legal or financial advice.
- Sensitive information.
US_SOCIAL_SECURITY_NUMBER,CREDIT_DEBIT_CARD_NUMBERandUS_BANK_ACCOUNT_NUMBERon ANONYMIZE in both directions, so a complaint containing a card number still gets answered with the number masked; BLOCK where refusing the turn outright is preferable.EMAIL,PHONEandADDRESSon ANONYMIZE,NAMEleft off. One regex for the company’s internal order-reference format, ANONYMIZE. - Word filters. Managed profanity on. Custom list containing three competitor brand names legal supplied.
- Contextual grounding. Grounding threshold 0.6, relevance threshold 0.5, tuned against an evaluation set built from the knowledge base.
- Automated Reasoning checks. The refund and cancellation-eligibility sections of the policy handbook uploaded as one policy, extracted, then corrected over two sittings against a dozen test questions Legal supplied. Applied to replies that assert eligibility. Because the check reports rather than blocks, the hand-off is application code:
INVALIDandTRANSLATION_AMBIGUOUSboth route the conversation to a human. - Invocation. The existing
Conversecall passesguardrailIdentifierandguardrailVersion, with the retrieved passages marked as the grounding source. Version is pinned in configuration and bumped through the release process when policy changes. If the order-lookup tools later move onto AgentCore, the same guardrail rides the model invocation and a Gateway policy covers the tool calls. - Observability. Guardrail trace enabled on every call and model invocation logging switched on, so each intervention lands in CloudWatch Logs with the policy and category that caught it. A metric filter over denied-topic interventions drives the alarm: if the rate doubles in an hour, either the replies have drifted or users have found a new way to ask the same thing.
The three production incidents all get caught inside one call. The pasted SSN is masked by the sensitive-information filter on input, so the model sees a placeholder. The competitor comparison trips denied topics on input or output. The fourteen-day refund claim trips the contextual grounding check against the thirty-day passage.
What’s worth remembering
- Bedrock Guardrails is a six-in-one safety surface. Content filters, denied topics, sensitive information, word filters, contextual grounding, and Automated Reasoning checks, one configuration, one call path, one version to pin.
- Denied topics are natural-language policy. A name, a definition, up to five sample phrases, up to 30 topics, describing a theme rather than listing names.
- PII filtering works in both directions. 31 built-in types plus regex, each set to BLOCK, ANONYMIZE or NONE, and per direction. Neither direction covers tool arguments or tool results, and the logs keep the unmasked original.
- Contextual grounding returns grounding and relevance scores on outputs, with thresholds between 0 and 0.99. The grounding source and the query have to be marked on the call, and AWS scopes the check to summarisation, paraphrasing and question answering rather than open chat.
- Automated Reasoning checks answer a different question from grounding. Grounding asks whether a passage supports the claim; automated reasoning asks whether the claim follows from a written rule, returning
VALID,INVALID,SATISFIABLE,IMPOSSIBLE,TRANSLATION_AMBIGUOUS,NO_TRANSLATIONSorTOO_COMPLEX, in detect mode only. - Prompt engineering reduces intervention rates without enforcing anything. Keep the system prompt; don’t treat it as the boundary.