Skip to content

fix(security): data-plane P1 findings from AISIX-Cloud#911 - #683

Merged
jarvis9443 merged 12 commits into
mainfrom
fix/security-911-dp
Jul 2, 2026
Merged

fix(security): data-plane P1 findings from AISIX-Cloud#911#683
jarvis9443 merged 12 commits into
mainfrom
fix/security-911-dp

Conversation

@jarvis9443

@jarvis9443 jarvis9443 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Fixes the data-plane P1 findings from the two security review docs tracked in api7/AISIX-Cloud#911. Each fix ships with a DP E2E that fails before and passes after. P2-and-lower findings are intentionally out of scope (documented as won't-fix on the issue).

Fixes api7/AISIX-Cloud#911

Findings addressed

  • [26] Mandatory guardrails weren't fail-closed. A mandatory guardrail whose remote backend failed open produced a Bypass verdict that was let through. Added a MandatoryGuardrail decorator (mirrors MonitorGuardrail) that converts BypassBlock for mandatory rows, so a mandatory guardrail that can't run blocks the request instead of silently passing it.

  • [22] Audio and raw passthrough ignored the per-model request timeout. /v1/audio/{transcriptions,translations,speech} and the passthrough tunnel dispatched to the upstream without applying model.request_timeout(), so a slow/blackholed provider could pin a request open past the configured deadline. All three sites fully buffer the response, so the E2E request timeout is the correct bound.

  • [27] Error-path metric model label was caller-controlled. On a pre-resolution failure (model-not-found) the typed endpoints recorded the raw client model string as a Prometheus label, allowing unbounded time-series creation (cardinality DoS). Unresolved models now collapse to a fixed unresolved sentinel via a shared helper (the typed-endpoint analogue of the passthrough PASSTHROUGH_MODEL_LABEL guard).

  • [21] TPM/TPD wasn't enforced on non-chat endpoints. completions / rerank / images / audio / messages / responses reserved the rate-limit layers but dropped the reservation uncommitted, so their token counters never moved — a caller could bypass token-rate limits by routing through them. Each non-streaming dispatch now commits the upstream-reported token total. The verbatim streaming paths of /v1/messages and /v1/responses still release their reservation on drop without post-stream token accounting; closing that requires threading the reservation into the end-of-stream guard (the streaming into_stream_hold pattern) and is tracked in Streaming /v1/messages and /v1/responses skip TPM/TPD + release concurrency early #688 — budget ($) already gates those requests via the cp-api ledger, so the residual gap is token-rate + concurrent-stream count.

  • [23] /v1/completions skipped output guardrails. It ran the input hook but relayed the completion text unscanned, so a content/DLP block enforced on /v1/chat/completions was bypassable via the legacy surface. It now buffers the completion text into a synthetic response and runs the resolved chain's output hook, mirroring chat/responses/messages. Because the upstream already billed when the block fires, the 422 carries the billed usage (marked guardrail_blocked) so cp-api's ledger doesn't under-report the charged spend.

  • [6] The raw passthrough tunnel skipped guardrails entirely. /passthrough/:provider/*rest forwarded verbatim with no scanning, so any configured content/DLP guardrail was bypassable through it. Following LiteLLM's passthrough default, the request and response bodies are now scanned as text (UTF-8-lossy) against the resolved chain; a block surfaces a redacted content_filter 422.

Behavior / compatibility

  • New blocks surface as content_filter 422 (typed) — same envelope shape already used by chat; the matched text is never echoed (only the guardrail name).
  • Passthrough now enforces content guardrails where it previously did not; operators relying on passthrough for large binary uploads should note the whole-body text scan runs against the resolved chain (no-op when no chain matches).
  • User-facing documentation for the new completions/passthrough guardrail coverage will be paired in an api7/docs PR.

Summary by CodeRabbit

  • New Features

    • Added mandatory guardrail enforcement: remote/unreachable guardrail evaluation errors now block requests when configured as mandatory.
    • Added input/output guardrail checks for passthrough requests.
    • /v1/completions now enforces output guardrails and can return content-filtered errors.
  • Bug Fixes

    • Improved token/quota accounting across audio, images, messages, responses, rerank, embeddings, chat, and completions (including correct commits and 501/error paths).
    • Audio and other model requests now respect configured per-model timeouts.
    • Reduced metrics label cardinality by using a bounded “resolved model” label (with an “unresolved” fallback).
  • Tests

    • Added end-to-end coverage for mandatory guardrails, completions output enforcement, token-commit/TMP behavior, timeouts, and bounded metric labels.

The Guardrail.mandatory field was parsed and stored but never read at
runtime — a remote guardrail (Bedrock/Azure/Aliyun) marked mandatory
still followed fail_open, so when its upstream was unreachable the
request proceeded unscanned (Bypass). default_fail_open() is true, so
an operator who set mandatory=true expecting fail-closed got silent
fail-open. mandatory means the opposite of what it did.

Wire it via a MandatoryGuardrail decorator (mirrors the existing
MonitorGuardrail/enforcement_mode pattern) applied outermost in
build_one: it upgrades a Bypass verdict to Block, overriding fail_open
on the failure path. Allow/Block pass through untouched and only remote
guardrails emit Bypass, so keyword rows are behaviourally unchanged.

Tests: mandatory turns input+output Bypass into Block; non-mandatory
keeps failing open; Allow/Block pass through unchanged.
The audio (transcription/translation/speech) and raw passthrough tunnel
dispatched directly to the upstream WITHOUT applying the model's
request_timeout, unlike every other non-streaming direct-upstream path
(count_tokens/rerank/responses, wired by #554). A slow or blackholed
provider could therefore pin one of these requests open past the model's
configured deadline, and the timeout-driven cooldown/failover never
engaged. All three sites fully buffer the response (.bytes()), so the
E2E request_timeout is the correct bound (no streaming to truncate).

Adds a DP E2E that drives a transcription against a stalling upstream and
asserts the request is abandoned well before the upstream would respond.
The typed proxy endpoints (chat/messages/completions/count_tokens/
embeddings/images/rerank/responses and audio speech) recorded the RAW
client-supplied `model` field as the Prometheus `model` label on their
error paths. That field is caller-controlled free text until it resolves
against the snapshot, so on a pre-resolution failure (model-not-found) a
caller could mint unbounded metric series by sending many unique unknown
model names — a metric-cardinality DoS.

Collapse any unresolved model to a fixed "unresolved" sentinel via a
shared usage_attr::metric_model_label helper (get_by_name covers direct
models and virtual routers alike), the typed-endpoint analogue of
passthrough's PASSTHROUGH_MODEL_LABEL guard (#451). The raw requested
name still flows to the per-request access log and usage events, which
are bounded by request volume rather than label cardinality.

Adds a DP E2E that fires many unique unknown model names at a typed
endpoint and asserts none leaks into an aisix_requests_total label and
they all collapse to model="unresolved".
…orced

completions, rerank, images, audio (transcription/translation/speech),
messages and responses reserved the multi-layer rate-limit but dropped
the reservation uncommitted, so their TPM/TPD (token-per-minute/day)
counters never moved. A caller could bypass token-rate limits by routing
traffic through any of these endpoints — only chat and embeddings
committed the actual token cost.

Each non-streaming dispatch now commits the upstream-reported token total
to every reserved layer (0 for endpoints/paths that consume no tokens,
e.g. TTS and the not-implemented branches), mirroring the embeddings
reference. The verbatim streaming paths of /v1/messages and /v1/responses
still release their reservation on drop without post-stream token
accounting — closing that requires threading the reservation into the
stream's end-of-stream guard (the chat.rs into_stream_hold + concurrency
hold pattern, #450/#108) across the failover loop, tracked as a focused
follow-up; budget ($) already gates those requests, so the residual gap
is token-rate only.

Adds a DP E2E: with TPM=10 and an upstream reporting 16 tokens, the first
/v1/completions call succeeds and the second is 429 (pre-fix the counter
stayed 0 and the second call also succeeded).
/v1/completions ran the input guardrail hook but relayed the model's
completion text unscanned. A content/DLP block enforced on
/v1/chat/completions was therefore bypassable by moving the response leg
to the legacy completions surface — the same output-hook gap #204 closed
for streaming chat and #448 for tool-call output.

After the upstream returns, buffer the completion choices' text into a
synthetic ChatResponse and run the resolved chain's output hook, mirroring
chat / responses / messages. A block surfaces the redacted content_filter
422 (naming only the guardrail, never the matched text, per #153/#519) and
the upstream tokens stay committed since the provider already billed them.

Adds a DP E2E: an innocent prompt against an upstream that emits a
forbidden word in its completion text is turned into a content_filter 422
that never carries the word (pre-fix the caller received it verbatim).
/passthrough/:provider/*rest forwarded requests and responses verbatim
with no guardrail scanning, so a tenant that configured a content/DLP
guardrail could bypass it entirely by routing traffic through passthrough.

Resolve the guardrail chain for the model whose credentials the tunnel
borrows and, following LiteLLM's passthrough default, scan the whole
request body (before the upstream call) and the whole response body (after)
as text against that chain. A block surfaces the redacted content_filter
422 that names only the guardrail, never the matched text (#153/#519).
Bodies are decoded UTF-8-lossy so binary payloads degrade to replacement
chars instead of being skipped; the scan is a no-op when no chain matches.

Adds a DP E2E covering both directions: a forbidden request body is
blocked before the upstream is called, and an upstream reply carrying a
forbidden word is blocked without the word reaching the caller.
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds mandatory guardrail enforcement, bounds metric model labels for unresolved models, commits quota reservations across proxy endpoints, and applies guardrail and timeout checks to completions and passthrough flows, with end-to-end coverage.

Changes

Mandatory Guardrail Enforcement

Layer / File(s) Summary
MandatoryGuardrail contract and wiring
crates/aisix-core/src/models/guardrail.rs, crates/aisix-guardrails/src/build.rs
Documents enforcement_mode fatal-error behavior, wires row.mandatory into build_one, and implements MandatoryGuardrail upgrading Bypass to Block.
Mandatory enforcement unit tests
crates/aisix-guardrails/src/build.rs
Adds AlwaysBypass stub and tests confirming Bypass-to-Block upgrade only when mandatory is set.

Bounded Metric Model Labels

Layer / File(s) Summary
metric_model_label helper
crates/aisix-proxy/src/usage_attr.rs
Adds UNRESOLVED_MODEL_LABEL sentinel and metric_model_label returning the sentinel for unresolved models.
Wiring bounded labels into handler error paths
crates/aisix-proxy/src/chat.rs, crates/aisix-proxy/src/completions.rs, crates/aisix-proxy/src/count_tokens.rs, crates/aisix-proxy/src/embeddings.rs, crates/aisix-proxy/src/images.rs, crates/aisix-proxy/src/messages.rs, crates/aisix-proxy/src/rerank.rs, crates/aisix-proxy/src/responses.rs, crates/aisix-proxy/src/audio.rs
Replaces raw model_name with metric_model_label(...) derived from the current snapshot in error-path metrics recording.
Metric cardinality e2e test
tests/e2e/src/cases/metric-cardinality-model-label-e2e.test.ts
Verifies bogus model names never leak into metric labels and are collapsed under model="unresolved".

Quota Token Commit Accounting

Layer / File(s) Summary
Audio endpoint reservation commit and timeout
crates/aisix-proxy/src/audio.rs
Captures reservation variables, applies per-model timeouts, and commits total or zero tokens after upstream calls.
Completions token commit
crates/aisix-proxy/src/completions.rs, tests/e2e/src/cases/completions-tpm-commit-e2e.test.ts
Captures the reservation, commits 0 tokens on 501/bridge-error branches, and verifies completions TPM usage is committed across calls.
Images endpoint token commit
crates/aisix-proxy/src/images.rs
Captures the reservation and commits computed or zero tokens across success/501/bridge-error branches.
Messages endpoint token commit
crates/aisix-proxy/src/messages.rs
Captures the reservation and commits prompt+completion tokens on the non-streaming success path.
Rerank endpoint token commit
crates/aisix-proxy/src/rerank.rs
Captures the reservation and commits total tokens from prompt_tokens after successful upstream dispatch.
Responses endpoint token commit
crates/aisix-proxy/src/responses.rs
Captures the reservation and commits prompt+completion tokens when usage is not handled by the stream.
Audio timeout e2e test
tests/e2e/src/cases/audio-timeout-e2e.test.ts
Verifies a slow whisper upstream is abandoned before completion per the model's timeout.

Proxy Endpoint Guardrail Enforcement

Layer / File(s) Summary
Completions output guardrail
crates/aisix-proxy/src/completions.rs
Synthesizes a ChatResponse from completion text and blocks via ProxyError::ContentFiltered on guardrail rejection.
Completions output guardrail e2e test
tests/e2e/src/cases/completions-output-guardrail-e2e.test.ts
Verifies a keyword output guardrail blocks a forbidden word in /v1/completions responses.
Passthrough guardrail resolution and input check
crates/aisix-proxy/src/passthrough.rs
Resolves the guardrail chain and blocks forbidden request bodies before forwarding upstream.
Passthrough timeout and output guardrail check
crates/aisix-proxy/src/passthrough.rs
Applies per-model timeouts and blocks forbidden upstream response bodies.
Passthrough guardrail e2e tests
tests/e2e/src/cases/passthrough-guardrail-e2e.test.ts
Verifies both output and input guardrail blocking behavior on /passthrough requests.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Passthrough
  participant GuardrailIndex
  participant Guardrail
  participant Upstream
  Client->>Passthrough: POST /passthrough/:provider/*rest
  Passthrough->>GuardrailIndex: resolve(model, api key, team)
  GuardrailIndex-->>Passthrough: guardrail chain
  Passthrough->>Guardrail: check_input(body as ChatFormat)
  alt input blocked
    Guardrail-->>Passthrough: Block
    Passthrough-->>Client: 422 ContentFiltered
  else input allowed
    Passthrough->>Upstream: forward request
    Upstream-->>Passthrough: response body
    Passthrough->>Guardrail: check_output(body as ChatResponse)
    alt output blocked
      Guardrail-->>Passthrough: Block
      Passthrough-->>Client: 422 ContentFiltered
    else output allowed
      Passthrough-->>Client: relay upstream response
    end
  end
Loading
sequenceDiagram
  participant Handler
  participant Quota
  participant Upstream
  participant Reservation
  Handler->>Quota: enforce(request)
  Quota-->>Handler: reservation
  Handler->>Upstream: dispatch request
  alt success
    Upstream-->>Handler: response + usage
    Handler->>Reservation: commit_tokens(prompt+completion)
  else not supported / bridge error
    Handler->>Reservation: commit_tokens(0)
  end
  Handler-->>Handler: return response or error
Loading

Possibly related PRs

  • api7/aisix#506: Both PRs modify build_one in crates/aisix-guardrails/src/build.rs, affecting how guardrail chains are constructed and wrapped.
  • api7/aisix#640: Both PRs modify crates/aisix-guardrails/src/build.rs to wrap guardrails based on enforcement settings, and this PR extends that pipeline with MandatoryGuardrail.
🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
E2e Test Quality Review ⚠️ Warning Audio timeout coverage only hits /v1/audio/transcriptions; /v1/audio/speech is a separate code path and has no E2E, so the PR doesn’t fully cover the audio timeout fix. Add a /v1/audio/speech E2E with a slow upstream and assert the request fails at the model timeout (and that the upstream was actually hit), plus any needed translation-route coverage.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is related to the PR’s security fixes and references the tracked P1 findings it addresses.
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.
Security Check ✅ Passed PASS: I checked the modified proxy/guardrail paths and found no secret leakage, auth bypass, ownership, TLS, or storage issues.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/security-911-dp

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

The mandatory-guardrail fail-closed change updated the field doc; dump-schema
picks that up as the JSON Schema description. Regenerated to clear drift.

@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: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/aisix-proxy/src/audio.rs (1)

446-464: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Map audio timeouts through the shared bridge-error conversion. req.timeout(d) makes the request fail with a timeout-shaped reqwest::Error, but both audio paths still wrap send() failures as BridgeError::Transport, so the per-model deadline won’t surface as a timeout or feed the same cooldown/failover path as rerank. Use reqwest_error_to_bridge in both send sites.

🤖 Prompt for 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.

In `@crates/aisix-proxy/src/audio.rs` around lines 446 - 464, The audio request
send path is wrapping all `req.send()` failures as `BridgeError::Transport`, so
timeout failures from `req.timeout(d)` are not converted consistently with other
bridge paths. Update both audio send sites in `audio.rs` to route the
`reqwest::Error` through `reqwest_error_to_bridge` before calling
`note_failure`, using the existing request flow around
`req.send()`/`req.timeout(d)` and the `BridgeError` handling in the audio
transcription/translation code.
🧹 Nitpick comments (2)
tests/e2e/src/cases/completions-tpm-commit-e2e.test.ts (1)

89-114: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Possible flake at TPM window boundary.

If the rate limiter uses a fixed per-minute window, running the two sequential postCompletion() calls right at a minute boundary could reset the counter between calls, causing an intermittent false failure since the second call would then return 200. Worth confirming the limiter's windowing semantics against the CI cadence.

🤖 Prompt for 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.

In `@tests/e2e/src/cases/completions-tpm-commit-e2e.test.ts` around lines 89 -
114, The TPM commit e2e test can flake at a minute boundary because the two
sequential postCompletion() calls may land in different fixed windows and reset
the counter between requests. Update the test in
completions-tpm-commit-e2e.test.ts, specifically the second /v1/completions
assertion in the test around postCompletion(), so it avoids relying on
wall-clock boundary timing by anchoring the calls within the same window or
making the setup wait for a stable window before sending the first request.
crates/aisix-core/src/models/guardrail.rs (1)

460-464: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Rewrite this as public API reference text.

This docstring exposes internal implementation details (DP, MandatoryGuardrail, Bypass, Block) in a model comment that feeds public schema/API docs. Describe the observable contract instead: when mandatory is enabled, evaluation failures from remote guardrails reject the request even if fail_open is enabled. As per coding guidelines, "Write descriptions in Admin API resource models and OpenAPI definitions as public API reference text, not internal implementation notes. Avoid internal shorthand (DP, CP, kine row, wire shape, mock server, bridge dispatch, issue-only context)."

🤖 Prompt for 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.

In `@crates/aisix-core/src/models/guardrail.rs` around lines 460 - 464, Rewrite
the doc comment on the guardrail model field to describe only the public
behavior of the mandatory setting, not the implementation details. In the
`guardrail.rs` model docs, replace references to internal terms like `DP`,
`MandatoryGuardrail`, `Bypass`, and `Block` with public API language stating
that when `mandatory` is enabled, remote guardrail evaluation failures reject
the request even if `fail_open` is set. Keep the wording suitable for
schema/OpenAPI reference text and aligned with the observable contract of the
field.

Source: Coding guidelines

🤖 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-guardrails/src/build.rs`:
- Around line 395-406: The MandatoryGuardrail::enforce path converts
GuardrailVerdict::Bypass into a Block via GuardrailVerdict::block(...), which
loses guardrail_name. Update the bypass-to-block handling in
MandatoryGuardrail::enforce to construct the Block variant explicitly and
preserve guardrail_name using self.row_name.clone(), so downstream 422 envelopes
keep the correct name.

In `@crates/aisix-proxy/src/completions.rs`:
- Around line 273-320: The output guardrail block path in `completions.rs` loses
billed usage metadata after `bridge.complete()` succeeds and
`reservation.commit_tokens(total_tokens)` runs, so `dispatch()` falls back to a
zeroed `emit_error_usage_event(...)` path. Update the `check_output()` /
`ProxyError::ContentFiltered` handling around `resolved_chain`, `ChatResponse`,
and `reservation.commit_tokens` to mirror the charged-failure pattern used in
`chat.rs`, preserving `model_id`, `provider_key_id`, and upstream usage when
returning the 422 error.

In `@crates/aisix-proxy/src/messages.rs`:
- Line 470: The streamed `/v1/messages` path is skipping
`reservation.commit_tokens(...)`, so it can bypass TPM/TPD enforcement. Update
the `messages.rs` stream flow around `usage_handled_by_stream` and the stream
completion guard to carry the reservation keys through completion, then invoke
post-stream token accounting with the terminal usage just like the chat stream
path. Make sure the fix is applied in both the reservation setup site and the
later stream finalization block referenced by the comment.

In `@crates/aisix-proxy/src/passthrough.rs`:
- Around line 309-339: The passthrough request flow is reserving quota before
the input guardrail check, so blocked requests still consume RPM. Move the quota
reservation in passthrough.rs to after the guardrail branch that uses
Guardrail::check_input and returns ProxyError::ContentFiltered, mirroring the
ordering used in audio.rs. Keep the reservation tied to the successful path
only, so rejected passthrough bodies never call quota::enforce() before the
upstream is reached.

In `@crates/aisix-proxy/src/responses.rs`:
- Line 349: The streamed /v1/responses path is skipping quota reconciliation
because usage_handled_by_stream bypasses reservation.commit_tokens(...) without
any post-stream token accounting. Update the responses streaming flow in
responses.rs to mirror the chat stream path: carry the reservation keys/handle
into the stream completion guard, and on terminal usage call post-stream token
accounting before releasing the reservation. Ensure the fix is applied around
the reservation setup and streaming completion logic so streamed responses still
count against TPM/TPD.

In `@tests/e2e/src/cases/metric-cardinality-model-label-e2e.test.ts`:
- Around line 113-125: The unknown-model POSTs in the metric cardinality e2e
test are being swallowed, so the test can pass without proving the request path
ran. Update the loop in metric-cardinality-model-label-e2e.test.ts to capture
each fetch response (or thrown error) and assert the expected failure/status for
the bogus model requests instead of using a catch that ignores everything. Use
the existing request block around BOGUS_COUNT and the /v1/chat/completions fetch
to verify each call actually reaches the server and fails as intended before the
scrape assertions run.

In `@tests/e2e/src/cases/passthrough-guardrail-e2e.test.ts`:
- Around line 145-166: The passthrough guardrail test depends on a prior test’s
propagation wait, so it can race when run alone or in a different order. Update
the `forbidden request body is blocked by the input guardrail before the
upstream is called` test to synchronize itself by waiting for guardrail/config
propagation before calling `passthrough`, or move that readiness setup into the
suite’s `beforeAll`. Use the existing `waitConfigPropagation()` helper (or the
same readiness logic used elsewhere in this file) so the test does not rely on
execution order.

---

Outside diff comments:
In `@crates/aisix-proxy/src/audio.rs`:
- Around line 446-464: The audio request send path is wrapping all `req.send()`
failures as `BridgeError::Transport`, so timeout failures from `req.timeout(d)`
are not converted consistently with other bridge paths. Update both audio send
sites in `audio.rs` to route the `reqwest::Error` through
`reqwest_error_to_bridge` before calling `note_failure`, using the existing
request flow around `req.send()`/`req.timeout(d)` and the `BridgeError` handling
in the audio transcription/translation code.

---

Nitpick comments:
In `@crates/aisix-core/src/models/guardrail.rs`:
- Around line 460-464: Rewrite the doc comment on the guardrail model field to
describe only the public behavior of the mandatory setting, not the
implementation details. In the `guardrail.rs` model docs, replace references to
internal terms like `DP`, `MandatoryGuardrail`, `Bypass`, and `Block` with
public API language stating that when `mandatory` is enabled, remote guardrail
evaluation failures reject the request even if `fail_open` is set. Keep the
wording suitable for schema/OpenAPI reference text and aligned with the
observable contract of the field.

In `@tests/e2e/src/cases/completions-tpm-commit-e2e.test.ts`:
- Around line 89-114: The TPM commit e2e test can flake at a minute boundary
because the two sequential postCompletion() calls may land in different fixed
windows and reset the counter between requests. Update the test in
completions-tpm-commit-e2e.test.ts, specifically the second /v1/completions
assertion in the test around postCompletion(), so it avoids relying on
wall-clock boundary timing by anchoring the calls within the same window or
making the setup wait for a stable window before sending the first request.
🪄 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: 28893282-42b9-4a6e-a9f5-ed385936c34d

📥 Commits

Reviewing files that changed from the base of the PR and between d0de1c1 and f1761af.

📒 Files selected for processing (18)
  • crates/aisix-core/src/models/guardrail.rs
  • crates/aisix-guardrails/src/build.rs
  • crates/aisix-proxy/src/audio.rs
  • crates/aisix-proxy/src/chat.rs
  • crates/aisix-proxy/src/completions.rs
  • crates/aisix-proxy/src/count_tokens.rs
  • crates/aisix-proxy/src/embeddings.rs
  • crates/aisix-proxy/src/images.rs
  • crates/aisix-proxy/src/messages.rs
  • crates/aisix-proxy/src/passthrough.rs
  • crates/aisix-proxy/src/rerank.rs
  • crates/aisix-proxy/src/responses.rs
  • crates/aisix-proxy/src/usage_attr.rs
  • tests/e2e/src/cases/audio-timeout-e2e.test.ts
  • tests/e2e/src/cases/completions-output-guardrail-e2e.test.ts
  • tests/e2e/src/cases/completions-tpm-commit-e2e.test.ts
  • tests/e2e/src/cases/metric-cardinality-model-label-e2e.test.ts
  • tests/e2e/src/cases/passthrough-guardrail-e2e.test.ts

Comment thread crates/aisix-guardrails/src/build.rs
Comment thread crates/aisix-proxy/src/completions.rs
Comment thread crates/aisix-proxy/src/messages.rs
Comment thread crates/aisix-proxy/src/passthrough.rs
Comment thread crates/aisix-proxy/src/responses.rs
Comment thread tests/e2e/src/cases/metric-cardinality-model-label-e2e.test.ts
Comment thread tests/e2e/src/cases/passthrough-guardrail-e2e.test.ts
…etric fixes

- mandatory guardrail: preserve row name when upgrading Bypass to Block so
  the 422 envelope names the guardrail instead of an unnamed content-filter
- passthrough: reserve rate-limit layers AFTER the input guardrail so a
  content block doesn't burn an RPM slot (matches typed endpoints)
- metric-cardinality e2e: assert the error status on each unresolved request
  instead of swallowing it, so a regression that resolves them is caught
- passthrough-guardrail e2e: self-synchronize the input-block test on its own
  readiness gate instead of relying on the output-block test's propagation
…block path

The #911 [23] output guardrail on /v1/completions returns 422 AFTER the
upstream billed and reservation.commit_tokens ran. Returning a bare error made
the handler fall onto emit_error_usage_event with zero tokens, dropping
model_id / provider_key_id / usage for a request the provider already charged —
under-reporting to cp-api's budget ledger and /logs.

Mirror responses.rs #543 / chat.rs UpstreamCharge: on an output block carry the
billed usage on the success struct marked guardrail_blocked and return the
redacted 422 body, so the UsageEvent keeps the real counts. E2E asserts the
block is recorded on the charged path (provider=openai) not the zeroed error
path (provider=unknown).
… follow-up

Point the messages.rs / responses.rs streaming-reservation comments at the
tracked issue (#688) instead of a vague 'tracked follow-up', per CodeRabbit
review on #683.
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