auth: support model alias claims with body rewrite#42
Conversation
The platform's MODEL_HF_PATH_OVERRIDES alias was previously one-way: the JWT model claim and the orchestrator's TOML model.name both held the canonical HF path, but envs that hard-code the user-facing name (e.g. emilschmitz/curriculum-oversight's LLM judge sending sprints/Llama-3.2-1B-Instruct) hit 403 because the canonical-only claim required exact-string match. RftClaims gains an optional model_aliases list. allows_model accepts alias hits, and pin_and_check_model variants (typed, string, JSON) plus the transparent-proxy fallback rewrite the request body's model field to self.model after an alias-only match, so vLLM only ever sees the canonical served name. Base-model and LoRA matches pass through unchanged.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 5f2c4ad. Configure here.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5f2c4adcd0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| pub fn canonical_for_alias(&self, requested: &str) -> Option<String> { | ||
| if !self.is_model_alias(requested) { | ||
| return None; | ||
| } | ||
| let base = self.model.as_deref()?; | ||
| if base.is_empty() || base == requested { | ||
| return None; | ||
| } | ||
| Some(base.to_string()) |
There was a problem hiding this comment.
Preserve LoRA names before alias rewrites
When a signed JWT contains a model_aliases entry that is also the allowed LoRA name (or a step-versioned LoRA alias), canonical_for_alias() returns the base model before checking whether requested is a LoRA match. The typed handlers then rewrite model to the base, so a request intended to use the run's adapter is forwarded as a base-model request instead, corrupting LoRA-scoped runs under that alias configuration; the helper's contract says LoRA targets must pass through unchanged.
Useful? React with 👍 / 👎.
ApprovabilityVerdict: Needs human review Changes to auth module ( You can customize Macroscope's approvability policy. Learn more. |
Bugbot/Codex flagged: if a JWT lists the same string in both model_aliases and lora (or as a <lora>-... step adapter), the prior canonical_for_alias would rewrite the request to the base model, silently swapping a LoRA call for a base-model call. Fix is to consult the LoRA branch before rewriting — LoRA matches now take precedence, so the original name reaches vLLM and dispatch works. Adds tests for both the exact-lora and step-versioned-lora collisions.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 18a80dfc0a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if self.is_model_alias(requested) { | ||
| return true; |
There was a problem hiding this comment.
Require a base model before authorizing aliases
When a JWT contains model_aliases but omits model (or sets it empty, which this struct permits via #[serde(default)]), this branch authorizes the alias even though canonical_for_alias later returns None because there is no canonical base to rewrite to. In that misconfigured/lora-only token case the server forwards the alias unchanged on chat/completions/embeddings/responses, broadening the JWT scope instead of failing closed; alias authorization should be gated on a non-empty base model or handled as an error.
Useful? React with 👍 / 👎.
Codex P2: a JWT with model_aliases but no model claim (or an empty model) would authorize the alias via allows_model and then forward it to vLLM unchanged, because canonical_for_alias has no canonical to rewrite to. That broadens JWT scope instead of failing closed. is_model_alias now requires a non-empty base model claim, so aliases without a canonical fall back to the same scope as the no-aliases case.
|
@codex review |
|
Codex Review: Didn't find any major issues. Breezy! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |

Summary
RftClaimsgains an optionalmodel_aliases: Vec<String>.allows_modelaccepts alias hits, and all pin-and-check sites + the transparent-proxy fallback rewrite the request body'smodelfield to the canonicalself.modelafter an alias-only match.sprints/Llama-3.2-1B-Instruct) are now authorized, and vLLM still only sees the canonical HF path.Needs a matching platform change to populate
model_aliasesin the JWT (incoming PR).Note
High Risk
Changes JWT model authorization and request-body rewriting on security-sensitive inference paths; incorrect alias/LoRA precedence could mis-route or broaden access.
Overview
JWT run-scoped auth now accepts optional
model_aliasesonRftClaims, so clients can send a platform-facing model id (e.g.sprints/...) while the token’s canonicalmodelstays the HF path vLLM expects.After authorization, alias-only requests have their
modelrewritten to the JWT base in the transparent proxy and in allpin_and_check_model*paths; base model and LoRA names are not rewritten, including when an alias collides with LoRA (LoRA wins). Aliases require a non-empty basemodel; empty alias entries and alias-without-base fail closed.Depends on the platform populating
model_aliasesin issued JWTs.Reviewed by Cursor Bugbot for commit 11e5481. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Support model alias claims with body rewrite in JWT auth and proxy handlers
model_aliasesfield toRftClaimsin auth.rs; JWTs can now carry a list of alternate model names that are authorized alongside the base model.canonical_for_aliasto resolve an alias back to the canonical base model, skipping rewrite for LoRA dispatches.modelfield in the JSON body to the canonical base model when an alias is matched, before forwarding the request.Macroscope summarized 11e5481.