Skip to content

fix(quota): run input guardrails before the rate-limit reservation (#542) - #561

Merged
moonming merged 1 commit into
mainfrom
fix/issue-542-guardrail-before-enforce
Jun 8, 2026
Merged

fix(quota): run input guardrails before the rate-limit reservation (#542)#561
moonming merged 1 commit into
mainfrom
fix/issue-542-guardrail-before-enforce

Conversation

@moonming

@moonming moonming commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

What

A guardrail-blocked request burned an RPM/RPD/RPS/RPH slot. /v1/messages, /v1/responses, /v1/embeddings ran check_input after crate::quota::enforce, and Reservation::drop only releases the concurrency permit — it never refunds the request counter. So a content-policy refusal counted against the caller's quota. /v1/chat/completions already runs guardrails before the reservation precisely to avoid this (its own code comment says so).

How

Hoist the resolve-chain + check_input block above quota::enforce on all three surfaces. (messages was pre-existing; responses + embeddings inherited the ordering from #541/#544.) The budget pre-check ordering is unchanged. Deliberately not fixed via a Reservation::drop refund — that would also refund slots on upstream 5xx/timeouts, a separate policy decision affecting every surface.

Test plan

blocked_request_does_not_consume_rate_limit_slot: API key capped at RPM=1, a blocking guardrail; a blocked request (422) followed by a benign one — the benign request still returns 200 (pre-fix it got 429 because the block burned the only slot). The existing per-surface input-block tests still pass (the guardrail blocks correctly in its new position). fmt + clippy clean; 408 aisix-proxy lib tests pass.

Surfaced by the independent pre-fix audit of the #719 follow-up batch. Refs #542, #719.

Summary by CodeRabbit

  • Improvements

    • Requests blocked by input guardrails and content policies are now evaluated before rate-limit reservation. This prevents blocked requests from consuming your quota, allowing a higher proportion of your rate limit to serve legitimate requests.
  • Tests

    • Added test to verify that content-blocked requests do not consume rate limit slots.

)

A guardrail-blocked request burned an RPM/RPD/RPS/RPH slot: /v1/messages,
/v1/responses, /v1/embeddings ran check_input AFTER crate::quota::enforce, and
Reservation::drop only releases the concurrency permit — it never refunds the
request counter. So a content-policy refusal counted against the caller's
quota. /v1/chat/completions already runs guardrails BEFORE the reservation
specifically to avoid this.

Hoist the resolve-chain + check_input block above quota::enforce on all three
surfaces (messages pre-existing; responses + embeddings widened by #541/#544).
Budget pre-check ordering is unchanged. Not fixed via Reservation::drop refund
— that would also refund slots on upstream failures, a separate policy.

Test: blocked_request_does_not_consume_rate_limit_slot — RPM=1, a blocked
request then a benign one; the benign request still returns 200 (pre-fix it
got 429 because the block burned the slot). fmt + clippy clean; 408 lib tests
pass.
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Free

Run ID: 38d6f1dd-47fe-4149-a3d3-c67e718af39b

📥 Commits

Reviewing files that changed from the base of the PR and between 963068f and f48ce10.

📒 Files selected for processing (3)
  • crates/aisix-proxy/src/embeddings.rs
  • crates/aisix-proxy/src/messages.rs
  • crates/aisix-proxy/src/responses.rs

📝 Walkthrough

Walkthrough

Three request handlers reorder quota enforcement to occur after input guardrail checks, preventing content-policy blocks from consuming rate-limit slots. A test verifies blocked requests preserve the quota reservation.

Changes

Guardrail-before-quota reordering

Layer / File(s) Summary
Guardrail-before-quota reordering across handlers
crates/aisix-proxy/src/embeddings.rs, crates/aisix-proxy/src/messages.rs, crates/aisix-proxy/src/responses.rs
Embeddings, messages, and responses handlers relocate ModelRateLimit construction and crate::quota::enforce() calls to occur after input guardrail checks resolve, preventing blocked requests from consuming RPM slots. Documentation updates explain the ordering requirement and issue references (#542, #719).
Rate-limit slot preservation test
crates/aisix-proxy/src/responses.rs
New test blocked_request_does_not_consume_rate_limit_slot verifies that with rpm=1, a guardrail-blocked /v1/responses request returns 422 and does not consume the quota slot, allowing a subsequent benign request to succeed with 200.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes


Note

🎁 Summarized by CodeRabbit Free

Your organization has reached its limit of developer seats under the Pro Plan. For new users, CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please add seats to your subscription by visiting https://app.coderabbit.ai/login.If you believe this is a mistake and have available seats, please assign one to the pull request author through the subscription management page using the link above.

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

@moonming
moonming merged commit 69c7685 into main Jun 8, 2026
8 checks passed
@moonming
moonming deleted the fix/issue-542-guardrail-before-enforce branch June 8, 2026 08:00
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