Amazon Bedrock Guardrails
The Amazon Bedrock Guardrails middleware scans requests and responses against a guardrail you configure natively in Amazon Bedrock, without hand-writing a raw JSON template.
Set guardrailId and guardrailVersion to identify an existing guardrail. The middleware authenticates to AWS, sends the request
and response text to that guardrail for evaluation, and blocks or forwards traffic based on the returned verdict. Configuration,
thresholds, and denied topics are managed in the AWS console, not in Hub.
Key Features
- Native AWS guardrail evaluation: reuses a guardrail you already configured in Amazon Bedrock instead of reimplementing its rules in Hub.
- Request and response scanning: enable either direction independently, or both.
- Every client request format: works with
custom,ccr,responsesAPI,messagesAPI, andmcptraffic. - Flexible AWS authentication: static credentials, a session token for temporary credentials, or the default AWS credential chain.
- Blocking and masking: the guardrail's own policy decides whether a match blocks the content or anonymizes it in place.
Requirements
-
AI Gateway must be enabled:
helm upgrade traefik traefik/traefik -n traefik --wait \--reset-then-reuse-values \--set hub.aigateway.enabled=true -
A guardrail already created in Amazon Bedrock, with its guardrail identifier and a published version. See Amazon Bedrock Guardrails for how to create one.
-
AWS credentials allowed to call
bedrock:ApplyGuardrailon the guardrail. See Amazon Bedrock Guardrails permissions.
How It Works
- Reads the client request according to
clientRequestFormat, extracting the fields to scan. Either the format's predefined fields, or the paths you list injsonQueriesfor acustomformat.
When a Chat Completion, Responses API, Messages API, Bedrock Mantle, Google Agent Platform, or MCP middleware precedes this guardrail in the chain, it marks the client's request format in the request context.
The guardrail then uses that format automatically, and no clientRequestFormat value is required. Left unset with no such middleware in the chain, it falls back to custom.
- Calls the guardrail in the configured
region, identified byguardrailIdandguardrailVersion, once per configured direction (request,response, or both). - Applies the guardrail's verdict:
- A blocking policy returns a deny response (
onDenyResponse). - An anonymizing policy, such as a sensitive-information filter set to mask, rewrites the matched text in place with the replacement Bedrock returns, and the request or response continues.
- Otherwise the request or response continues unchanged.
- A blocking policy returns a deny response (
- Authenticates to AWS using one of:
- Static
accessKeyIdandsecretAccessKey, optionally with asessionTokenfor temporary credentials. - The default AWS credential chain (for example, an IAM role for a Kubernetes service account), when no static credentials are set.
- Static
Configuration Examples
- Request and response, static credentials
- Custom format, IAM role
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: bedrock-guardrails
spec:
plugin:
amazon-bedrock-guardrails:
accessKeyId: urn:k8s:secret:bedrock-guardrails-credentials:access-key-id
secretAccessKey: urn:k8s:secret:bedrock-guardrails-credentials:secret-access-key
region: us-east-1
guardrailId: abcd1234efgh
guardrailVersion: '1'
request: {}
response:
onDenyResponse:
statusCode: 200
message: "This response was blocked by a content policy."
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: bedrock-guardrails-custom
spec:
plugin:
amazon-bedrock-guardrails:
region: us-east-1
guardrailId: abcd1234efgh
guardrailVersion: '1'
clientRequestFormat: custom
request:
jsonQueries:
- .prompt
With no static accessKeyId/secretAccessKey set, the middleware falls back to the default AWS credential chain, so the second example authenticates through an IAM role attached to the pod instead.
Reference
General
| Parameter | Description | Required | Default |
|---|---|---|---|
region | AWS region the guardrail is deployed in | Yes | |
guardrailId | Identifier of the Bedrock guardrail to call | Yes | |
guardrailVersion | Published version of the guardrail | Yes | |
accessKeyId | AWS access key ID for static credentials | No | |
secretAccessKey | AWS secret access key for static credentials | No | |
sessionToken | AWS session token, for temporary credentials alongside accessKeyId/secretAccessKey | No | |
clientRequestFormat | Client payload format: custom, ccr, responsesAPI, messagesAPI, or mcp | No | custom |
request | Enables guardrail evaluation on the request. At least one of request/response is required | No | |
response | Enables guardrail evaluation on the response. At least one of request/response is required | No |
accessKeyId and secretAccessKey must be set together. sessionToken requires both of them to also be set.
request / response
| Parameter | Description | Required | Default |
|---|---|---|---|
request.jsonQueries | JSON paths to scan in the request body. Only allowed for clientRequestFormat: custom | No | |
request.onDenyResponse | Custom deny response when the guardrail blocks the request | No | |
request.onDenyResponse.statusCode | HTTP status code (100-599) | No | 403 |
request.onDenyResponse.message | Response body, sent as-is. Left empty, the guardrail's own blocked message is used | No | |
request.onDenyResponse.contentType | Response Content-Type | No | matches clientRequestFormat |
response.jsonQueries | JSON paths to scan in the response body. Only allowed for clientRequestFormat: custom | No | |
response.onDenyResponse | Custom deny response when the guardrail blocks the response | No | |
response.onDenyResponse.statusCode | HTTP status code (100-599) | No | 403 |
response.onDenyResponse.message | Response body, sent as-is. Left empty, the guardrail's own blocked message is used | No | |
response.onDenyResponse.contentType | Response Content-Type | No | matches clientRequestFormat |
onDenyResponse.statusCode doesn't apply when an MCP response is streamed as SSE. The status is already sent, so the block arrives as a JSON-RPC error event on the original 200.
jsonQueries is rejected when clientRequestFormat has a predefined extraction path (ccr, responsesAPI, messagesAPI, mcp). It only applies to custom.
clientRequestFormat and deny response formatting
You can set clientRequestFormat explicitly, or leave it unset and let the middleware detect it automatically from the request context. Either way, once a format is active, the deny response body is automatically formatted to match the client's expected format:
clientRequestFormat | Non-streaming response | Streaming response |
|---|---|---|
custom | Raw message text | Raw message text |
ccr | Chat Completion JSON with message as assistant content | SSE chunk (data: {...}) |
responsesAPI | Responses API JSON with status: "failed" and message in a structured error object | SSE events (response.failed) with the same status/error shape |
messagesAPI | Messages API JSON with message as assistant text content | SSE events (message_delta with stop_reason: refusal) |
mcp | JSON-RPC error echoing the request id | JSON-RPC error as an SSE data: event |
For a streaming client, the deny response's HTTP status code matters as much as its body shape. Most
streaming clients treat any non-2xx status as a transport error. They retry the connection instead of
reading the body. At a non-2xx status, this means the structured deny message above doesn't reach the
user. The client only sees repeated connection failures. Set onDenyResponse.statusCode to 200 so the
client reads the deny body as a normal, refused turn.
