Uh oh!
There was an error while loading. Please reload this page.
fix(passthrough): enforce the body model's rate limit on the raw tunnel - #805
Conversation
Pre-fix the passthrough handler passed None to quota::enforce, so a Model's inline rate_limit and model-scope policies never applied to passthrough traffic. For provider endpoints with no typed surface (e.g. video generation) the configured model cap was unenforceable anywhere in the product while the dashboard accepted it silently. Post-fix the top-level `model` field of a JSON passthrough body is matched (exact display_name, same provider) against the configured Models and its limits are reserved through the same quota::enforce layers as the typed endpoints. Non-JSON bodies, bodies without a model field, and unregistered names keep the previous behavior: request-level layers only. The tunnel still parses no usage, so only request-count dimensions (rps/rpm/rph) draw from the model buckets. api7/AISIX-Cloud#1116
Warning Review limit reached
Next review available in:17 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The provider's async journey is submit -> poll -> fetch. Polling is a bodyless GET with no model field, so it must keep flowing after the submit cap is hit — a client that submitted right before the cap must still be able to poll its task to completion.
…t full DOM Audit follow-ups on the body-model rate-limit fix: - The tunnel forwards bodies verbatim, so callers typically name the provider-native id (model_name), not the gateway alias. Match display_name first, then model_name within the addressed provider (min_by_key(id) keeps duplicate-model_name picks deterministic); key the bucket by the entry's display_name either way so tunnel and typed traffic share one budget. - Replace the full serde_json::Value parse with a #[derive(Deserialize)] field probe — unknown fields skip without allocating, so a large body no longer costs a DOM copy of itself. - New tests: model-scope policy row gates the tunnel; provider-native model_name resolves the alias's cap; a same-named model of another provider is never charged; e2e asserts the 429 produced no upstream round-trip.
moonming
commented
Jul 23, 2026
@coderabbitai review |
✅ Action performedReview finished.
|
Uh oh!
There was an error while loading. Please reload this page.
What
/passthrough/:provider/*restnow enforces the model-level rate-limit layers (a Model's inlinerate_limitplusmodel-scope rate-limit policies) for JSON bodies whose top-levelmodelfield names a configured Model of the addressed provider.Tracking:
api7/AISIX-Cloud#1116(field report: a video model's configured rate limit was silently ignored on passthrough).Why
Pre-fix, the tunnel called
quota::enforce(&state, auth, None)— only the API-key / team / member layers applied. For provider endpoints with no typed surface (video generation being the reported case), the model cap configured in the dashboard was unenforceable anywhere in the product, and nothing surfaced that: the config was accepted and silently inert.How
body_model_rate_limit()inpassthrough.rs: best-effort parse of the buffered request body (already in memory for the input-guardrail scan), exactdisplay_namelookup constrained to the addressed provider, then the existingModelRateLimit::from_model→quota::enforce(Some(&model_rl))path used by every typed endpoint. Same bucket keys as typed traffic, so passthrough and typed calls to the same model share one budget.modelfield, unregistered name, or a same-named Model of a different provider → request-level layers only, no error.Ecosystem comparison (per repo rule 7)
modelfield — the shape shared by OpenAI-compatible bodies and e.g. Alibaba Model Studio's synthesis APIs (https://help.aliyun.com/zh/model-studio/text-to-image-v2-api-reference).modelfield — and apply per-model request caps when the name matches a registered model; identity-level (key/team) limits apply on all routes regardless. None skip the model layer wholesale for lack of a "resolved model".Scope notes
a2a.rs/mcp.rsalso passNone, but have no model concept — intentionally untouched.api7/AISIX-Cloud#1116. This PR changes rate limiting only — credential resolution and the security: enforce model ACLs for routing targets and passthrough providers #449 ACL semantics are untouched.rate_limitandmodel-scope policies are existing CP surfaces; this is a DP enforcement gap only.Audit remediation (cold audit: MERGE-WITH-CHANGES, 3 MEDIUM / 0 HIGH)
modelnow also matches the provider-nativemodel_namewithin the addressed provider (exactdisplay_namefirst); the bucket is keyed by the entry'sdisplay_nameeither way, so tunnel and typed traffic share one budget.min_by_key(id)keeps duplicate-model_namepicks deterministic.serde_json::ValueDOM replaced by a#[derive(Deserialize)]field probe; unknown fields skip without allocation.model-scopeRateLimitPolicyrow gating the tunnel (no inlinerate_limit).display_namethrough the sameModelRateLimit::from_model— and the alias test covers the bucket-key path.Accepted residuals (by design, per the fallback contract): (a) a caller that omits or misnames the body
modelfield draws only the key/team layers — model caps on the raw tunnel bind well-behaved clients; the polling leg of async APIs is intentionally exempt. (b) A key not ACL'd for model X can still drain X's shared bucket through the tunnel, because credential borrowing picks the first accessible model and the body model is not ACL-checked — pre-existing behavior, tracked with the deferred credential-resolution step inapi7/AISIX-Cloud#1116.Tests
crates/aisix-proxy/src/lib.rs):passthrough_enforces_model_rate_limit_from_body_model_field— rpm=1 model, second tunnel call 429rate_limit_exceeded.passthrough_unregistered_or_absent_body_model_keeps_key_layer_only— unregistered name and non-JSON body pass; key-level cap still gates (pre-fix behavior pinned).tests/e2e/src/cases/passthrough-model-rate-limit-e2e.test.ts, source-blind, real gateway binary + etcd + mock upstream):Retry-Afterand standard envelope;aisix-proxysuite: 660 passed. Existing passthrough e2e family: green (one unrelated/v1/responsesleast-busy case flaked with ECONNRESET in a parallel run and passes alone; it does not traverse the changed code).