Repository navigation
fix(api): stop restricted-key /v1/models leaking aggregator rows via namespaced root - #15452
Merged
diegosouzapw merged 2 commits intoOct 6, 2026
Conversation
…namespaced root The catalog filter admitted a provider row when either its id or its raw `root` passed isModelAllowedForKey. `root` carries no owning-provider namespace, so an aggregator row such as cline's `cline/deepseek/deepseek-v4-flash` (root `deepseek/deepseek-v4-flash`) was read as the deepseek provider's model and listed for a key that only allowed `ds/deepseek-v4-flash`. The same applied to any provider wildcard (`openrouter/*` admitted cline's `openrouter/free`). The check moves to isCatalogModelAllowedForKey: the id decides; a bare root still matches a bare allowlist entry (cheaper-inference#781), a namespaced root never does, and a row whose id is blocked is not re-admitted through its root. Closes cheaper-inference#15409
Keeps src/app/api/v1/models/catalog.ts under its frozen file-size baseline: the call site shrinks to one line and the helper resolves its own @/lib/db/apiKeys dependencies (tests still inject them).
Contributor
Author
|
CI note for reviewers: The red checks on this PR are inherited from
The checks that exercise this change pass: API Route Typecheck, the unit-test shards and Vitest. Before pushing I ran the |
diegosouzapw
merged commit Oct 6, 2026
6fc4375
into
cheaper-inference:release/v3.8.52
44 of 51 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #15409
Problem
A restricted API key's
GET /v1/modelslisted models from providers the key never allowed. The catalog filter admitted a provider row when either itsidor its rawrootpassedisModelAllowedForKey.rootcarries no owning-provider namespace, so an aggregator row such as cline'swas read as the deepseek provider's model. A key allowing only
ds/deepseek-v4-flashtherefore listed the cline row. The same happened with any provider wildcard:openrouter/*admitted cline'sopenrouter/free. Because the blocked-pattern check ran separately inside each of the two calls, blockingcline/*didn't help either: therootbranch re-admitted the row.Fix
The check moves into
isCatalogModelAllowedForKey(src/app/api/v1/models/catalogKeyFilter.ts):iddecides;rootstill matches a bare allowlist entry such asgpt-4o→openai/gpt-4o(the reason the root check was added in [Feature] Return only accessible models from /models for restricted API keys #781);root(contains/) never grants access;idis blocked is never re-admitted through itsroot.catalog.tscalls the helper in one line, which keeps the file under its frozen size baseline.Relation to #15415
#15415 (@HouMinXi), opened today against the same issue, fixes a different over-match: in
modelPermissions.tsit stops aprovider/*wildcard such ascline/*from covering another provider nested under the same prefix. That is worth having, but it does not touch the path in this report. Here the key has no cline entry, and the cline row gets in through the catalog'sisModelAllowedForKey(key, m.root)branch.I checked this by running this PR's regression test on #15415's head (
6a6b681775), with the catalog's originalid || rootcheck in place. R1 (deepseek-only key lists cline rows), R2 (openrouter/*admits cline'sopenrouter/free) and R4 (blocked id re-admitted throughroot) still fail there. The two PRs are complementary and touch different files, so they can merge in either order.Tests
tests/unit/api-key-allowlist-root-namespace-15409.test.tsuses real DB keys and the realisModelAllowedForKeyalias expansion:ds/deepseek-v4-flashkey does not listcline/…orcl/…, and still listsdeepseek/deepseek-v4-flashopenrouter/*key does not list cline'sopenrouter/freegpt-4oentry still matchesopenai/gpt-4o([Feature] Return only accessible models from /models for restricted API keys #781)cline/*row is not re-admitted through a bare rootcatalog.tsroutes through the helperR1, R2, R4 and R5 failed before the fix. All pass after it. Related suites:
api-key-policy,apikeypolicy-disable-non-public,api-manager-provider-permissions,models-catalog-*andcombo-restricted-key-target-policy-12886, 219/219 passing.Local quality gates
I ran the
quality.ymlfast-gate set locally againstrelease/v3.8.52, including typecheck:core, dashboard typecheck, the ts7 diagnostics ratchet, ESLint--max-warnings 0on the changed files, and changelog integrity:cyclesandagent-skills-syncfail identically on the untouched base.workflowsneedsactionlintandzizmor, which aren't installed locally; this PR doesn't touch workflows.