Server-side content filtering and policy enforcement for Claude Code running on
Amazon Bedrock. Guardrails operate at the API layer -- they apply regardless of
client configuration, local hook bypasses, or --print mode gaps.
Verification update (2026-05-30). Several claims in earlier drafts of this document have been revised based on live testing in us-east-1 against Claude Haiku 4.5 / Sonnet 4.6 with a fully-configured guardrail. Reproducible scripts and per-test evidence are in
bedrock-guardrails-test-evidence.mdandtests/aws-guardrails/. Key corrections incorporated below:
- Prompt Attack filter works today via the
contentPolicyConfigfilter type (does not require input tags). Earlier "does NOT work" claim was based on a separate legacy mechanism.InvocationsBlockedmetric does not exist. UseInvocationsIntervened.- Streaming intervention does NOT raise an error; it returns the
blockedInputMessagingtext as a normal stream delta. Customise the message — see the dedicated section below for verified examples.
AWS Bedrock Guardrails provide seven configurable protection policies that
inspect both model inputs and outputs. For enterprise Claude Code deployments,
they serve as a server-side defense layer that complements this kit's
local hooks (pii-guard.sh, git-guard.sh) and deny rules.
Key value:
- Works on
--printmode where local hooks do not fire (see platform-compensations.md) - Cannot be bypassed by the developer or the model
- Provides CloudWatch metrics for monitoring and alerting
- Applies uniformly across all team members without per-machine configuration
| # | Policy | Works with Claude Code? | Notes |
|---|---|---|---|
| 1 | Content Filters | Yes | 5 categories + PROMPT_ATTACK; input + output |
| 2 | Prompt Attack Filter (as a Content Filter type) | Yes | Verified: fires today via contentPolicyConfig.filtersConfig with type: PROMPT_ATTACK, no input tags required |
| 3 | Denied Topics | Yes | Custom topic definitions; semantic matching has measurable FPR (see calibration note below) |
| 4 | Word Filters | Yes | Profanity + custom list (up to 10,000 items) |
| 5 | Sensitive Information Filters | Yes | PII detection + custom regex; pre-built catalogue is US-centric |
| 6 | Contextual Grounding Checks | Conditional | Hallucination detection — but errors any request without an explicit grounding_source. Do NOT enable for general code-gen workloads |
| 7 | Automated Reasoning Checks | Yes | Logical rule validation |
Detects and blocks content across five harmful categories:
| Category | Description |
|---|---|
| Hate | Discriminatory or prejudicial content |
| Insults | Demeaning or offensive language |
| Sexual | Sexually explicit material |
| Violence | Threats or graphic violence |
| Misconduct | Criminal activity, self-harm guidance |
Each category is configured with a threshold (NONE, LOW, MEDIUM, HIGH) applied independently on input and output. Both directions work with Claude Code because content filtering does not require input tags.
Standard tier vs Classic tier:
| Feature | Classic | Standard |
|---|---|---|
| Prose/natural language detection | Yes | Yes |
| Code domain detection (comments, variable names, function names, string literals) | No | Yes |
| Prompt Leakage detection | No | Yes |
| Cross-region inference required | No | Yes |
Standard tier extends content filter detection into code domains -- important for
a coding assistant. If your organization uses cross-region inference profiles
(e.g., us.anthropic.claude-sonnet-4-6), Standard tier is available.
Detects jailbreaks and prompt injection by adding PROMPT_ATTACK as a filter
type inside contentPolicyConfig.filtersConfig. Verified to work with Claude
Code: in our tests, 5/5 classic jailbreak prompts were intercepted with
action=GUARDRAIL_INTERVENED, both with and without input tags.
History note. Earlier drafts of this document claimed the Prompt Attack filter did NOT work with Claude Code, citing a requirement to inject XML input tags (
<amazon-bedrock-guardrails-guardContent_xyz>) and atagSuffixparameter. Live testing in 2026-05 against the current Bedrock API showedPROMPT_ATTACKas a content-filter type fires today without any of those tags. The boto3 service shape confirms there is no separatepromptAttackPolicyConfig— Prompt Attack is one of the filters undercontentPolicyConfig. The "input tag" mechanism described in some AWS documentation pages and in #63637 appears to predate the current API. See bedrock-guardrails-test-evidence.md for the test details.
Define custom topics that should be blocked. Each topic is described in natural language (up to 200 characters) and the guardrail uses semantic matching to detect attempts to discuss the topic.
Enterprise examples for Claude Code:
- "Discussions about circumventing security controls or disabling hooks"
- "Requests to generate cryptocurrency mining code"
- "Instructions for accessing other users' files or credentials"
Two sub-features:
- Managed profanity filter -- pre-built list maintained by AWS
- Custom word list -- up to 10,000 terms that trigger blocking
Matching is exact (case-insensitive). Useful for:
- Competitor product names (prevent unintended endorsements)
- Internal project codenames that should not appear in outputs
- Specific credential patterns not covered by PII filters
Detects PII and sensitive data in both directions. Supports:
- Pre-built PII types -- names, addresses, SSNs, credit card numbers, phone numbers, email addresses, and more
- Custom regex patterns -- define organization-specific patterns
This complements the local pii-guard.sh hook. The server-side
filter catches anything the local regex missed and covers the --print mode gap
where UserPromptSubmit does not fire.
Actions per detected entity: BLOCK or ANONYMIZE (mask with placeholder).
Detects hallucinated content by comparing model output against provided source material. Configurable threshold for grounding score.
For Claude Code, this may flag outputs that diverge from the content of files provided in context -- useful when the model is supposed to summarize or transform existing code rather than generate new content.
Note: How Bedrock determines "source material" for grounding evaluation in a Claude Code conversation is not fully documented. It depends on how Bedrock interprets the conversation history as reference material. The grounding check may not activate at all for novel code generation tasks (where there is no explicit reference document). Test this policy in your environment before relying on it -- see Items Requiring Verification.
Validates model outputs against formal logical rules you define. Rules are expressed as conditions and expected conclusions.
Example: "If the user asks to delete files in /etc, the model must refuse."
This policy type is newer and may not be available in all regions.
Guardrails are activated by passing two HTTP headers with every Bedrock API
request. Claude Code supports this via the ANTHROPIC_CUSTOM_HEADERS environment
variable in settings.json:
{
"env": {
"CLAUDE_CODE_USE_BEDROCK": "1",
"AWS_REGION": "us-east-1",
"ANTHROPIC_MODEL": "us.anthropic.claude-sonnet-4-6",
// Bedrock Guardrails — replace with your guardrail ID and version
"ANTHROPIC_CUSTOM_HEADERS": "X-Amzn-Bedrock-GuardrailIdentifier: your-guardrail-id\nX-Amzn-Bedrock-GuardrailVersion: 1"
}
}This pattern is already present (commented out) in settings-linux-macos.jsonc.
Header reference:
| Header | Value | Description |
|---|---|---|
X-Amzn-Bedrock-GuardrailIdentifier |
Guardrail ID (e.g., abc123def456) |
Identifies which guardrail to apply |
X-Amzn-Bedrock-GuardrailVersion |
Version number or DRAFT |
Which version of the guardrail configuration |
Multiple headers are separated by \n (literal newline) in the environment
variable value.
A minimal guardrail configuration that uses the policies known to work with Claude Code:
{
// Terraform or AWS CLI equivalent — shown as logical structure
"name": "claude-code-enterprise",
"description": "Server-side guardrails for Claude Code on Bedrock",
// VERIFIED 2026-05-30 — see "Streaming UX gotcha" section below.
// These messages flow back verbatim to the client (max 500 chars each).
// Default is "BLOCKED_INPUT_BY_GUARDRAIL" / "BLOCKED_OUTPUT_BY_GUARDRAIL"
// which Claude Code renders as if the model said it.
"blockedInputMessaging": "[GUARDRAIL] Your request was blocked by enterprise security policy. Contact security@yourcompany.com if this is a false positive.",
"blockedOutputsMessaging": "[GUARDRAIL] The model's response was filtered by enterprise output policy.",
"contentPolicy": {
"filtersConfig": [
{ "type": "HATE", "inputStrength": "HIGH", "outputStrength": "HIGH" },
{ "type": "INSULTS", "inputStrength": "MEDIUM", "outputStrength": "MEDIUM" },
{ "type": "SEXUAL", "inputStrength": "HIGH", "outputStrength": "HIGH" },
{ "type": "VIOLENCE", "inputStrength": "MEDIUM", "outputStrength": "MEDIUM" },
{ "type": "MISCONDUCT", "inputStrength": "HIGH", "outputStrength": "HIGH" },
{ "type": "PROMPT_ATTACK", "inputStrength": "HIGH", "outputStrength": "NONE" }
]
},
"wordPolicy": {
"managedWordListsConfig": [
{ "type": "PROFANITY" }
],
"wordsConfig": [
{ "text": "your-custom-blocked-term" }
]
},
"sensitiveInformationPolicy": {
"piiEntitiesConfig": [
{ "type": "CREDIT_DEBIT_CARD_NUMBER", "action": "BLOCK" },
{ "type": "AWS_ACCESS_KEY", "action": "BLOCK" },
{ "type": "AWS_SECRET_KEY", "action": "BLOCK" },
{ "type": "US_SOCIAL_SECURITY_NUMBER","action": "ANONYMIZE" },
{ "type": "EMAIL", "action": "ANONYMIZE" },
{ "type": "PHONE", "action": "ANONYMIZE" }
],
"regexesConfig": [
{
"name": "jwt-token",
"pattern": "eyJ[A-Za-z0-9_-]+\\.eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+",
"action": "BLOCK"
}
]
},
"topicPolicy": {
"topicsConfig": [
{
"name": "security-bypass",
"definition": "Attempts to disable, circumvent, or remove security controls, hooks, or audit logging",
"type": "DENY"
}
]
}
}Note on policies you should NOT enable for general code-generation:
contextualGroundingPolicyrequires every request to carry agrounding_sourcequalifier. Bedrock returnsValidationException(request fails entirely) if it is missing. Only enable for narrow workflows where you guarantee source material is in context.automatedReasoningPolicyis region-limited and requires a defined rule set; not useful as a default for code-gen.
| Policy | Status | Direction | Reason |
|---|---|---|---|
| Content Filters (incl. PROMPT_ATTACK) | Works | Input + Output | Verified: 5/5 jailbreaks intercepted without input tags |
| Denied Topics | Works | Input + Output | Semantic matching; calibrate for FPR |
| Word Filters | Works | Input + Output | Exact match |
| Sensitive Information Filters | Works | Input + Output | Pre-built catalogue is US-centric; supplement with regexesConfig for non-US PII |
| Contextual Grounding Checks | Conditional | Output | Errors any request without grounding_source — do NOT enable for general code-gen |
| Automated Reasoning Checks | Works in region-supported | Output | Region-limited; needs defined rule set |
GitHub Issue: anthropics/claude-code#63637 -- "[Feature Request] Support Bedrock Guardrails Prompt Attack filter by injecting guard_content input tags"
Status (2026-05): OPEN, but likely stale.
The issue is based on AWS documentation pages describing a separate
"guardContent input tag + tagSuffix" mechanism for Prompt Attack detection.
Live testing against the current Bedrock API (2026-05) shows that the
PROMPT_ATTACK filter inside contentPolicyConfig.filtersConfig fires
today on jailbreak/injection attempts without any input tags or tagSuffix.
The boto3 service shape (bedrock.create_guardrail) lists no separate
promptAttackPolicyConfig — only contentPolicyConfig.
Two possibilities, neither fully proven from the outside:
- The "guardContent input tag" mechanism still exists for an advanced
detection path that requires explicit user/system content separation, and
the simpler
PROMPT_ATTACKcontent filter type is a different (lower-resolution) feature that happens to work without tags. - The input-tag mechanism has been deprecated and replaced by the content-filter-type approach; AWS docs for it haven't been updated.
For a production deployment in 2026-05, enable PROMPT_ATTACK in
contentPolicyConfig — it provides material protection today against
classic jailbreak and injection attempts, regardless of how #63637 ultimately
resolves.
┌─────────────────────────────────────────────────────────────────────────┐
│ Developer Workstation │
│ │
│ User Input │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Claude Code Local Hooks │ │
│ │ • pii-guard.sh (PII/secrets scan) │ │
│ │ • git-guard.sh (git operation control) │ │
│ │ • audit-logger.sh (append-only audit) │ │
│ └───────────────────────┬─────────────────────────┘ │
│ │ (blocked if PII/secrets detected) │
│ ▼ │
└──────────────────────────┼──────────────────────────────────────────────┘
│ HTTPS (Bedrock API)
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ AWS Bedrock │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Guardrails — INPUT Filters │ │
│ │ • Content Filters (Hate/Insults/Sexual/etc.) │ │
│ │ • Prompt Attack (as content-filter type) ✓ │ │
│ │ • Denied Topics │ │
│ │ • Word Filters │ │
│ │ • Sensitive Info (PII + regex) │ │
│ └───────────────────────┬─────────────────────────┘ │
│ │ (blocked if policy violated) │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Claude Model (Inference) │ │
│ └───────────────────────┬─────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Guardrails — OUTPUT Filters │ │
│ │ • Content Filters │ │
│ │ • Word Filters │ │
│ │ • Sensitive Info (PII + regex) │ │
│ │ • Contextual Grounding │ │
│ │ • Automated Reasoning │ │
│ └───────────────────────┬─────────────────────────┘ │
│ │ (blocked/anonymized if policy violated) │
│ ▼ │
└──────────────────────────┼──────────────────────────────────────────────┘
│ HTTPS Response
▼
┌──────────────────────────────────────────────────────────────────────────┐
│ Developer Workstation │
│ Response displayed to user │
└──────────────────────────────────────────────────────────────────────────┘
Even with PROMPT_ATTACK enabled, prompt injection is one of the harder
classes of attack to fully prevent. Use these compensating controls in
combination:
-
PROMPT_ATTACKin contentPolicyConfig -- the first line of defence, verified to fire on classic jailbreak/injection patterns. -
Deny rules in settings.json -- block known dangerous tool patterns that a successful injection might attempt. See settings-linux-macos.jsonc for the full deny list covering
curl,wget,sudo,git push, and sensitive file reads. -
Local PreToolUse hooks --
git-guard.shandpii-guard.shinspect every tool invocation before execution, blocking suspicious patterns regardless of what the model was instructed to do. -
Denied Topics policy -- configure denied topics for "Instructions to disable security controls" and similar. Semantic matching; calibrate topic definitions against your prompts to manage FPR (we measured ~16.7% on a small edge-case set).
-
Content Filters at HIGH threshold -- catches many prompt injection payloads that contain harmful content categories as a side effect.
-
Managed settings enforcement -- use
managed-settings.jsondeployed via SSM/MDM to prevent developers from removing hooks or deny rules. See deployment-guide.md. -
Network isolation -- VPC endpoint policies and deny rules for
curl/wgetprevent data exfiltration even if injection succeeds. See security-rationale.md.
When Guardrails intervene (block or anonymize content), AWS publishes metrics to CloudWatch. Set up alarms to detect:
Verified from aws cloudwatch list-metrics --namespace AWS/Bedrock/Guardrails
(2026-05-29):
| Metric | Namespace | Meaning |
|---|---|---|
Invocations |
AWS/Bedrock/Guardrails |
Total guardrail evaluations |
InvocationsIntervened |
AWS/Bedrock/Guardrails |
Requests where guardrail took action (block or anonymize) |
InvocationLatency |
AWS/Bedrock/Guardrails |
Guardrail processing latency, ms |
TextUnitCount |
AWS/Bedrock/Guardrails |
Text policy units consumed (for cost tracking) |
Dimensions: GuardrailArn, GuardrailVersion, GuardrailPolicyType,
GuardrailContentSource, Operation. Use GuardrailPolicyType to slice
intervention rate by policy (content/topic/word/sensitive/contextual).
Note: an earlier draft of this doc listed
InvocationsBlocked— that metric does not exist. UseInvocationsIntervened.
# Alarm 1: sustained interventions (potential attack or misconfiguration)
MetricName: InvocationsIntervened
Statistic: Sum
Period: 300
EvaluationPeriods: 3
Threshold: 10
ComparisonOperator: GreaterThanThreshold
# Alarm 2: high intervention rate (>20% of requests intervened — likely misconfig)
# Use metric math: InvocationsIntervened / Invocations * 100
Threshold: 20
# Alarm 3: guardrail latency regression
MetricName: InvocationLatency
Statistic: p99
Period: 300
EvaluationPeriods: 2
Threshold: 1000 # ms; baseline observed in tests was 130–230ms
ComparisonOperator: GreaterThanThreshold
Enable Bedrock model invocation logging to capture full guardrail traces:
- S3 bucket for long-term storage
- CloudWatch Logs for real-time search
The trace includes which policy triggered, the matched content, and the action
taken. This integrates with the local audit chain from audit-logger.sh to
give end-to-end visibility.
When a guardrail blocks a prompt sent via
InvokeModelWithResponseStream (which Claude Code uses by default), Bedrock
does not raise an HTTP error or stream_error event. Instead, the client
receives a structurally-valid streaming response whose first (and only)
content_block_delta carries the literal text from
blockedInputMessaging — by default BLOCKED_INPUT_BY_GUARDRAIL.
Claude Code renders this as the model's reply, so the user sees:
> Please charge my card 4111-1111-1111-1111
BLOCKED_INPUT_BY_GUARDRAIL
| Property | Verified value |
|---|---|
blockedInputMessaging flows verbatim in non-streaming response |
✅ exact match |
blockedInputMessaging flows verbatim as a single stream content_block_delta |
✅ exact match (delta count = 1) |
blockedOutputsMessaging (when output filter fires) flows verbatim, both modes |
✅ exact match |
Special characters preserved (<script>, &, ", CJK, emoji) |
✅ no escape, no mangle |
| Min length | 1 char (empty rejected client-side) |
| Max length | 500 chars — 1000 chars rejected with ValidationException: Member must have length less than or equal to 500 |
| String templating (e.g., interpolating policy name into the message) | ❌ not supported; the string is returned exactly as configured |
Set both messages to a clearly non-model prefix so the user (and any UI on the receiving end) can tell this came from the guardrail, not the model. Both tested verbatim:
{
"blockedInputMessaging": "[GUARDRAIL] Your request was blocked by enterprise security policy. Contact security@yourcompany.com if this is a false positive.",
"blockedOutputsMessaging": "[GUARDRAIL] The model's response was filtered by enterprise output policy."
}Stay under 500 chars per field. If you need more detail (e.g., an internal ticket link), keep the customer-facing message short and put the rest in a runbook the support email leads to.
Because the guardrail message arrives through the same channel as model output, client code can't tell them apart at the protocol level. Two pragmatic options:
-
Distinctive prefix (this kit's recommendation): pick a literal prefix you would never expect from the model (
[GUARDRAIL],🚫, etc.) and have a local hook scan output for it. If matched, surface the message viastderrinstead of letting it look like model text. -
Watch CloudWatch
InvocationsIntervenedout-of-band: this won't change the in-session UX but lets ops see when interventions fire. See the monitoring section above.
This wire behaviour is consistent across Mac, Linux EC2, and Windows EC2 (verified 2026-05).
Most items in earlier drafts have now been verified live (see bedrock-guardrails-test-evidence.md). The remaining items still to validate in your own environment:
| # | Item | Status | Why your env may differ |
|---|---|---|---|
| 1 | Streaming intervention shape | ✅ verified — see "Streaming UX gotcha" above | Behaviour stable across SDKs/regions in our tests, but Claude Code's specific rendering of the magic string may change between releases. |
| 2 | CloudWatch metric names & namespace | ✅ verified AWS/Bedrock/Guardrails with 4 metric names |
AWS occasionally adds metrics; re-check list-metrics after major Bedrock releases. |
| 3 | PII detection thresholds | ✅ verified — Bedrock pre-built catalogue is US-centric | Country-specific PII (NRIC, EU phone formats) needs regexesConfig patterns. |
| 4 | Cross-region inference + Guardrails | ✅ verified for us.* and global.* |
Other inference-profile prefixes may exist for new regions. |
| 5 | Standard tier availability | ✅ available in us-east-1 | Re-check in your target region; schema is tierConfig.tierName="STANDARD" + crossRegionConfig. |
| 6 | Denied Topics FPR | ✅ measured ~16.7% on small edge-case set | Calibrate against your actual prompt distribution — your topic definitions and example data will differ. |
| 7 | Version handling | ✅ verified | DRAFT / numeric / invalid all behave as expected; bad-id and bad-version produce identical errors. |
| 8 | Latency impact | ✅ measured +18% p50 / +21% p95 | Will vary by policy complexity; re-measure after adding many regex patterns or denied topics. |
| 9 | PROMPT_ATTACK recall in your environment |
partial — measured 5/5 on classic jailbreaks | Test against your domain's actual injection attempts; some specific patterns may slip. |
| 10 | Contextual Grounding for code-gen | ✅ verified — errors any request without grounding_source |
Do NOT enable for general code-gen; only for narrow workflows with guaranteed source material. |
| 11 | blockedInputMessaging / blockedOutputsMessaging customisation |
✅ verified — verbatim, max 500 chars, special chars OK | If you need >500 chars, summarise + link to runbook. |
- AWS Bedrock Guardrails -- Components
- AWS Bedrock Guardrails -- Content Filters
- AWS Bedrock Guardrails -- Prompt Attacks
- AWS Bedrock Guardrails -- Input Tagging
- AWS Bedrock Guardrails -- Word Filters
- AWS Bedrock Guardrails -- Sensitive Information
- GitHub Issue #63637 -- Support Bedrock Guardrails Prompt Attack filter
- Related docs in this repository:
- pii-guard.md -- local PII/secrets scanning hook
- platform-compensations.md -- Windows
--printmode gap and Bedrock Guardrails as compensation - settings-linux-macos.jsonc -- reference settings with guardrail headers
- security-rationale.md -- overall security architecture
{ "contentPolicyConfig": { "filtersConfig": [ // ...other filters {"type": "PROMPT_ATTACK", "inputStrength": "HIGH", "outputStrength": "NONE"} ] } }