Skip to content

fix(guardrails): enforce monitor mode and fail output closed by default - #640

Merged
jarvis9443 merged 2 commits into
mainfrom
fix/guardrail-monitor-and-output-failclosed
Jun 23, 2026
Merged

fix(guardrails): enforce monitor mode and fail output closed by default#640
jarvis9443 merged 2 commits into
mainfrom
fix/guardrail-monitor-and-output-failclosed

Conversation

@jarvis9443

@jarvis9443 jarvis9443 commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Problem

Two guardrail behaviors contradicted their own configuration (architecture review item AISIX-Cloud#788 P1-3):

  1. enforcement_mode: monitor was never enforced. The DP stored the field, logged a warning at chain-build time, then hard-blocked anyway. Operators who set monitor expecting an audit/observe mode were actually rejecting traffic.
  2. Remote-guardrail output fail-open was inconsistent. Bedrock and Azure Prompt Shield (azure_content_safety) used a single fail_open (default true) for both the input and output hooks, so a provider outage on the output side released unscanned model output. The Azure/Aliyun text-moderation guardrails already split this with an independent output_fail_open defaulting to fail-closed.

Implementation

Monitor modebuild_one now applies enforcement_mode after building a row's runtime guardrail:

  • block (default): returned as-is.
  • monitor: wrapped in a MonitorGuardrail decorator that runs the rule exactly as configured but downgrades a Block verdict to Allow, emitting an audit log line (guardrail name + input/output hook + reason). It reports EndOfStreamCheck for streamed output so a rule that can never block does not force hold-back; it can never weaken a blocking peer because the chain folds to the strictest member.
  • unknown value: treated as block (fail-safe) with a warning.

chain.rs and index.rs are untouched — the decorator is fully contained in build.rs.

Output fail-closedBedrockConfig and AzureContentSafetyConfig gain an independent output_fail_open (#[serde(default)]false). The bedrock/prompt-shield dispatchers now pick the input-side fail_open for check_input and output_fail_open for check_output, mirroring the existing Azure/Aliyun text-moderation pattern. The runtime/published JSON Schema is regenerated via dump-schema.

Behavior changes

  • Monitor-mode guardrails observe and log instead of blocking.
  • The bedrock / azure_content_safety output hook now fails closed by default on a provider outage. Operators who prefer availability over correctness set output_fail_open: true. The input hook is unchanged.

Tests

  • DP standalone E2E (tests/e2e/src/cases/guardrail-monitor-mode-e2e.test.ts): creates a keyword rule in block mode, confirms it 422s, flips the same rule to monitor, and confirms the forbidden request now reaches the upstream — the block→monitor flip makes the assertion non-racy.
  • Unit tests: monitor downgrades Block→Allow on both hooks, does not force stream hold-back, unknown mode falls back to block; Bedrock/Prompt Shield output hook fails closed by default while input fail-open is unchanged, plus the opt-back-in path. Provider-failure fail-closed is exercised through the real HTTP/SDK wiremock path.

Docs: paired api7/docs PR documents enforcement_mode and the input/output fail-open split.

Part of api7/AISIX-Cloud#788

Summary by CodeRabbit

  • New Features

    • Added support for monitor enforcement mode for guardrails, allowing violations to be observed/logged without blocking requests.
  • Enhancements

    • Remote guardrails (Azure Content Safety Prompt Shield and Bedrock managed guardrails) now support an independent output_fail_open policy for the output hook, defaulting to fail-closed while the input hook continues using the existing top-level fail_open.
  • Testing / Validation

    • Updated unit and E2E coverage to verify monitor behavior and hook-specific failure handling.

Fixes api7/AISIX-Cloud#788

Two guardrail behaviors contradicted their configuration (AISIX-Cloud#788
P1-3).

enforcement_mode: monitor was stored but never enforced — the DP logged a
warning and then hard-blocked anyway, so operators who expected an audit
mode were actually rejecting traffic. build_one now wraps a monitor-mode
row in a MonitorGuardrail decorator that runs the rule exactly as configured
but downgrades a Block to Allow, emitting an audit log line ("would have
blocked") with the guardrail name and hook side. It also reports
EndOfStreamCheck for streamed output so a rule that can never block does not
force the response to hold back; it can never weaken a blocking peer because
the chain keeps the strictest member's policy. An unrecognized mode falls
back to block (fail-safe). chain.rs and index.rs are untouched.

Remote guardrail output fail-open was inconsistent: Bedrock and Azure Prompt
Shield used a single fail_open (default true) for both hooks, so a provider
outage on the output side released unscanned model output. They now carry an
independent output_fail_open (default false / fail-closed), matching the
Azure/Aliyun text-moderation guardrails. The input hook still follows the
outer fail_open; only the output hook changes its default.

Behavior changes:
- monitor-mode guardrails observe and log instead of blocking.
- Bedrock / azure_content_safety output hook fails closed by default on a
  provider outage. Operators who want the old behavior set
  output_fail_open: true.

Part of api7/AISIX-Cloud#788
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3bb20373-4e06-4628-8c7c-41e5dd1ec24c

📥 Commits

Reviewing files that changed from the base of the PR and between 033d14d and d8502f8.

📒 Files selected for processing (4)
  • crates/aisix-core/src/models/guardrail.rs
  • crates/aisix-guardrails/src/bedrock.rs
  • crates/aisix-guardrails/src/prompt_shield.rs
  • schemas/resources/guardrail.schema.json
✅ Files skipped from review due to trivial changes (1)
  • schemas/resources/guardrail.schema.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • crates/aisix-core/src/models/guardrail.rs
  • crates/aisix-guardrails/src/bedrock.rs
  • crates/aisix-guardrails/src/prompt_shield.rs

📝 Walkthrough

Walkthrough

Adds independent per-hook fail-open policies to Bedrock and Azure Content Safety guardrails: the input hook follows the existing outer fail_open, while output defaults to fail-closed via a new output_fail_open field. Separately, a MonitorGuardrail decorator is introduced that downgrades Block verdicts to Allow, implementing monitor enforcement mode with unit and E2E test coverage.

Changes

Per-hook output fail-open policy (Bedrock + Azure Content Safety)

Layer / File(s) Summary
Model fields and JSON schema contracts
crates/aisix-core/src/models/guardrail.rs, schemas/resources/guardrail.schema.json
Adds #[serde(default)] pub output_fail_open: bool to AzureContentSafetyConfig and BedrockConfig, and mirrors both as default: false boolean properties in the JSON schema.
Bedrock per-hook fail-open implementation
crates/aisix-guardrails/src/bedrock.rs
Adds output_fail_open to BedrockGuardrail, introduces fail_open_for(source) helper to select the correct policy, updates apply() and handle_failure to use per-request policy, and expands tests to assert hook-specific fail-open/closed behavior.
PromptShield per-hook fail-open implementation
crates/aisix-guardrails/src/prompt_shield.rs
Adds output_fail_open to PromptShieldGuardrail, updates shield and handle_failure to accept an explicit fail_open parameter, wires check_input/check_output to their respective policies, and adds tests for default output fail-closed and configurable output fail-open.

Monitor enforcement mode

Layer / File(s) Summary
MonitorGuardrail decorator and build_one wiring
crates/aisix-guardrails/src/build.rs
Refactors build_one to apply enforcement_mode via apply_enforcement_mode; introduces MonitorGuardrail which forwards all trait methods but downgrades Block verdicts to Allow and forces stream_output_policy to EndOfStreamCheck; unknown modes fall back to block. Unit tests cover all new behavioral paths.
Monitor mode E2E test
tests/e2e/src/cases/guardrail-monitor-mode-e2e.test.ts
Adds a Vitest E2E test that transitions a guardrail from block (expects 422) to monitor mode via PUT, waits for propagation, and asserts the request succeeds and reaches upstream.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately summarizes the two main changes: monitor mode enforcement and output fail-open defaults.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
E2e Test Quality Review ✅ Passed E2E test is complete and rigorous: full business flow (Etcd → DP → OpenAI upstream) validates monitor mode enforcement. Unit tests cover output_fail_open defaults (bedrock, prompt_shield). No block...
Security Check ✅ Passed PR adds output_fail_open boolean flags and MonitorGuardrail decorator without introducing credential leakage, unencrypted secret storage, authorization bypass, or TLS config errors. Existing creden...

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/guardrail-monitor-and-output-failclosed

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/aisix-core/src/models/guardrail.rs`:
- Around line 131-134: The doc comment for the fail-open policy field in the
guardrail model at lines 131-134 contains implementation details and internal
references that are not appropriate for public API documentation. Rewrite this
comment to focus on clear, operator-facing semantics by removing references to
"outer `fail_open`" and internal comparisons like "Mirrors the independent
output policy the Azure/Aliyun text-moderation guardrails already use". Describe
what the feature does from the user's perspective in straightforward language.
Apply the same user-facing approach to the additional comments at lines 356-359,
then regenerate the JSON schema to reflect the updated model documentation.

In `@crates/aisix-guardrails/src/bedrock.rs`:
- Line 519: The test in the BedrockGuardrail::new call is setting both the outer
fail_open parameter and output_fail_open to true, which means the test cannot
distinguish whether output is correctly using its own policy versus accidentally
using the input policy. Change the fourth parameter (the outer fail_open) from
true to false in the BedrockGuardrail::new call so that input and output
policies are properly distinguished during testing.

In `@crates/aisix-guardrails/src/prompt_shield.rs`:
- Around line 563-566: The output-policy tests are not properly verifying the
HTTP path and wiring because they can pass for the wrong reason (404/ConfigError
can also block). Replace the direct call to `handle_failure` in the opt-in test
with a call to `check_output` function and pass `fail_open=false` as a
parameter. This will prove that the output hook properly uses the
`output_fail_open` setting and actually exercises the intended HTTP path with
the 500 response from the mock at `/contentsafety/text:shieldPrompt`, rather
than just relying on unmatched mocks returning errors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c3f122c8-bf4c-42a2-b702-701dbceb1418

📥 Commits

Reviewing files that changed from the base of the PR and between 1524a20 and 033d14d.

📒 Files selected for processing (6)
  • crates/aisix-core/src/models/guardrail.rs
  • crates/aisix-guardrails/src/bedrock.rs
  • crates/aisix-guardrails/src/build.rs
  • crates/aisix-guardrails/src/prompt_shield.rs
  • schemas/resources/guardrail.schema.json
  • tests/e2e/src/cases/guardrail-monitor-mode-e2e.test.ts

Comment thread crates/aisix-core/src/models/guardrail.rs Outdated
Comment thread crates/aisix-guardrails/src/bedrock.rs Outdated
Comment thread crates/aisix-guardrails/src/prompt_shield.rs
Address CodeRabbit on #640:
- Reword output_fail_open schema descriptions to user-facing semantics (drop
  the internal 'outer fail_open' / 'mirrors Azure/Aliyun' phrasing); regenerate
  guardrail.schema.json.
- Bedrock opt-in test sets input fail_open=false so a passing output bypass
  proves the output hook uses output_fail_open, not the input policy; also
  assert input still blocks.
- Prompt Shield output tests drive the real HTTP path (path + api-version
  matcher, expect(1)) so the verdict can't come from an unmatched-route 404;
  the opt-in test runs check_output with input fail_open=false.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant