Skip to content

verify: capture the F5 Guardrails block (HTTP 200) live #1186

Open

Description

@tbolis-at-mulesoft

What needs verifying

The F5 Guardrails (F5 AI Guardrails / CalypsoAI) response shapes, captured live against a deployed proxy. The expected shape below comes from the policy's own contract (f5-guardrails-policy 1.0.0), not from a capture. Nobody has F5 AI Guardrails access yet: F5 offers no public trial, only a sales proof of concept. This issue owns the live capture, so #1177 can implement against a confirmed shape, or ship from the contract the way #1176 did and close.

Expected shape, which the live capture must confirm or correct:

  • Block: HTTP 200, not 403. The body is the caller's native refusal format, carrying the message Your request was blocked because it matched: <guardrail names> (phase request, scan <id>).:

    • OpenAI: a chat.completion with choices[0].finish_reason: "content_filter" and "model": "f5-guardrails";
    • Anthropic: stop_reason: "refusal";
    • Gemini: finishReason: "SAFETY".

    Some public docs still describe a 403. That was the shape of earlier versions; 1.0.0 returns 200.

  • Headers (prefix x-llm-proxy-f5-calypsoai-guardrail-):

    • -action: allow, flag, redact or reject;
    • -phase;
    • -reason: the display names of the scanners that fired;
    • -ids: the scanner IDs;
    • -scan-id: the F5 scan ID.
  • Streaming: a blocked call is never streamed. The block comes back as non-stream JSON even when the request had stream: true.

  • Non-blocking: flag and redact let the call continue, and redact rewrites the body with F5's masked text. With applyRedaction: false, a redact outcome becomes a 200 block. A failed redaction is also a 200 block, with -reason: redaction_failed.

  • Fail-closed: 503 with body {"error":{"message":"F5 AI Guardrails call failed","type":"service_error","code":"<reason>"}}. The reasons are auth_failed, bad_request, not_found, upstream_error, connection_error and parse_error. A missing verdict returns "F5 AI Guardrails returned no verdict" with none_response.

Why it blocks work

#1177 (detect the 200 block) needs a new 2xx refusal hook in the shared transport. Building that hook against an uncaptured shape risks one of two failures: misreading a genuine model content_filter stop as a block, or missing a real block entirely. The capture settles which header and body fields the hook can safely key on.

Cross-surface impact

Depends on

  • Donkey-Development-Kit/donkey-development-kit-provisioning#61: the live proxy, which needs F5 AI Guardrails credentials.

Related

Done when

  • A live 200 block captured for an OpenAI-format call: status, all x-llm-proxy-f5-calypsoai-guardrail-* headers and the exact body.
  • The same block captured on a stream: true call, confirming it arrives as non-stream JSON.
  • flag and redact outcomes captured, if reproducible.
  • A fail-closed 503 captured, if reproducible (for example with a bad token, which should give auth_failed).
  • The captures committed as fixtures for refusals: detect the F5 Guardrails block, which returns HTTP 200 #1177, and an F5 row added to docs/verified-apis.md as VERIFIED (LIVE).

Activity

  1. added
    enhancementNew feature or request
    blocked-on-verificationCannot be closed by writing code (build plan §0.3)
    size/s~half day, one file
    priority/p2Meaningful improvement, ship soon
    area:refusalsclassify(), the typed error taxonomy, handlers (1.2, 2.1, 2.2)
    on Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:refusalsclassify(), the typed error taxonomy, handlers (1.2, 2.1, 2.2)blocked-on-verificationCannot be closed by writing code (build plan §0.3)enhancementNew feature or requestpriority/p2Meaningful improvement, ship soonsize/s~half day, one file

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions