Skip to main content

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, and mcp traffic.
  • 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:ApplyGuardrail on the guardrail. See Amazon Bedrock Guardrails permissions.

How It Works​

  1. Reads the client request according to clientRequestFormat, extracting the fields to scan. Either the format's predefined fields, or the paths you list in jsonQueries for a custom format.
Automatic format detection

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.

  1. Calls the guardrail in the configured region, identified by guardrailId and guardrailVersion, once per configured direction (request, response, or both).
  2. 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.
  3. Authenticates to AWS using one of:
    • Static accessKeyId and secretAccessKey, optionally with a sessionToken for temporary credentials.
    • The default AWS credential chain (for example, an IAM role for a Kubernetes service account), when no static credentials are set.

Configuration Examples​

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."

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​

ParameterDescriptionRequiredDefault
regionAWS region the guardrail is deployed inYes
guardrailIdIdentifier of the Bedrock guardrail to callYes
guardrailVersionPublished version of the guardrailYes
accessKeyIdAWS access key ID for static credentialsNo
secretAccessKeyAWS secret access key for static credentialsNo
sessionTokenAWS session token, for temporary credentials alongside accessKeyId/secretAccessKeyNo
clientRequestFormatClient payload format: custom, ccr, responsesAPI, messagesAPI, or mcpNocustom
requestEnables guardrail evaluation on the request. At least one of request/response is requiredNo
responseEnables guardrail evaluation on the response. At least one of request/response is requiredNo

accessKeyId and secretAccessKey must be set together. sessionToken requires both of them to also be set.

request / response​

ParameterDescriptionRequiredDefault
request.jsonQueriesJSON paths to scan in the request body. Only allowed for clientRequestFormat: customNo
request.onDenyResponseCustom deny response when the guardrail blocks the requestNo
request.onDenyResponse.statusCodeHTTP status code (100-599)No403
request.onDenyResponse.messageResponse body, sent as-is. Left empty, the guardrail's own blocked message is usedNo
request.onDenyResponse.contentTypeResponse Content-TypeNomatches clientRequestFormat
response.jsonQueriesJSON paths to scan in the response body. Only allowed for clientRequestFormat: customNo
response.onDenyResponseCustom deny response when the guardrail blocks the responseNo
response.onDenyResponse.statusCodeHTTP status code (100-599)No403
response.onDenyResponse.messageResponse body, sent as-is. Left empty, the guardrail's own blocked message is usedNo
response.onDenyResponse.contentTypeResponse Content-TypeNomatches 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:

clientRequestFormatNon-streaming responseStreaming response
customRaw message textRaw message text
ccrChat Completion JSON with message as assistant contentSSE chunk (data: {...})
responsesAPIResponses API JSON with status: "failed" and message in a structured error objectSSE events (response.failed) with the same status/error shape
messagesAPIMessages API JSON with message as assistant text contentSSE events (message_delta with stop_reason: refusal)
mcpJSON-RPC error echoing the request idJSON-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.