fix(api): rerank silently drops provider nodes unless the hostname is local
OmniRoute Version
Docker image diegosouzapw/omniroute:3.8.50-web.
Also verified unchanged on release/v3.8.51 and on main — the filter quoted below is
byte-for-byte identical on all three (including its comment).
Installation Method
Docker / Docker Compose
Operating System
Linux
OS Version
Debian-based container host (NAS)
Node.js Version
No response (Docker image default)
Provider(s) Involved
Custom OpenAI-compatible rerank provider node
(prefix sf, baseUrl https://api.siliconflow.cn/v1).
Same node serves /v1/embeddings correctly.
Built-in registry provider siliconflow also involved (see "Impact" below).
Model(s) Involved
BAAI/bge-reranker-v2-m3
Client Tool
Any OpenAI-compatible client (reproduced with plain curl; originally hit via Hindsight / vectorize-io)
Description
POST /v1/rerank builds its local provider list from getCachedProviderNodes() but admits a
node only if its baseUrl hostname passes this filter
(src/app/api/v1/rerank/route.ts, v3.8.50 ≈ line 90; identical on release/v3.8.51 and main):
const hostname = new URL(n.baseUrl).hostname;
return hostname === "localhost" || hostname === "127.0.0.1"
|| /^172\.(1[6-9]|2[0-9]|3[0-1])\.\d{1,3}\.\d{1,3}$/.test(hostname);
Consequences:
Any node on a public hostname is dropped — and so is any node on
10.xor192.168.x, i.e. most real LAN setups.The drop is silent.
.filter()has noelseand noconsole.warn, and the enclosingtry/catchswallows with a comment calling it non-critical. A dropped node produces zero log output.The error message points at the wrong thing. After the filter,
localProviders.find(p => p.id === prefix)finds nothing, and the handler's final line returns:Invalid rerank model: <model>. Use format: provider/modelwhich sends the operator looking at the model name, when the actual cause is the node's
baseUrlhostname.The same node works on
/v1/embeddings. That path has no hostname admission gate: the prefix fallback atsrc/lib/embeddings/service.ts:236-274matches onn.prefix === providerwith no host condition. Hence the exact symptom "works on embeddings, fails on rerank".
Why this looks unintentional rather than a deliberate restriction
- #9096 already fixed a sibling gate on this same filter and was labelled
fix(providers): audio-speech/transcriptions/translations provider nodes silently unroutable; its body namesrerank/route.tsamong the affected files and has a section titled "Why this looks unintentional rather than a deliberate restriction". That fix removed the apiType condition; the hostname condition was left behind. - #6925 fixed the embeddings analogue by demoting the private-host test from admission
control to an auth-mode choice (
authType: isNoAuthLocal ? "none" : "apikey")./v1/reranknever received that change — the private-host test is still an admission gate here. - The inline comment claims "SSRF hardening", but real SSRF protection blocks outbound
requests to internal networks, whereas this allows only internal networks — the opposite
direction. The actual intent is visible in the
buildDynamicRerankProvidercomment ("Local OpenAI-compatible backends (oMLX, vLLM, etc.) expose /v1/rerank"): the regex is expressing the word local, not a security policy.
Impact: no path at all to a region-specific public rerank endpoint
For public SaaS rerank there are only two routes, and neither reaches a non-default region:
| Route | URL source | Can it be changed? |
|---|---|---|
Built-in registry (siliconflow, …) |
open-sse/config/rerankRegistry.ts — siliconflow.baseUrl = "https://api.siliconflow.com/v1/rerank" |
No. Hardcoded constant; a connection's baseUrl is never read |
provider_nodes |
the node's own baseUrl |
Only if the hostname passes the filter above (i.e. not a public host) |
So e.g. the China-region endpoint https://api.siliconflow.cn/v1/rerank is unreachable
through either route: the registry hardcodes .com, and a node pointing at .cn is
dropped by the hostname filter. (Both domains accept the same key; .cn is ~7x faster from
mainland China in my measurements — 0.17 s vs 1.26 s — so this is not academic.)
Note this is broader than my own case: PR #13732 documents the same failure for LAN and
Tailscale peers running TEI / Infinity / vLLM / llama.cpp, because 10.x and 192.168.x
are also excluded by the regex.
Steps to Reproduce
- Create a
provider_nodesentry:prefix = "sf",baseUrl = "https://api.siliconflow.cn/v1", apiType includesrerank, with a valid API key stored on the node. - Confirm the node works:
POST /v1/embeddings {"model":"sf/BAAI/bge-m3","input":"ping"}→ 200 (log line:EMBED Resolved custom embedding provider: sf -> https://api.siliconflow.cn/v1/embeddings). POST /v1/rerankwith{"model":"sf/BAAI/bge-reranker-v2-m3","query":"q","documents":["a","b"],"top_n":2}.
Expected Behavior
The node is used and a results array is returned.
Actual Behavior
{"error":{"message":"Invalid rerank model: sf/BAAI/bge-reranker-v2-m3. Use format: provider/model",
"type":"invalid_request_error"}}
No log entry anywhere indicating the node was rejected for its hostname.
Diagnostic short-cut for other operators
The three terminal errors map to three distinct stages, which makes triage possible without log spelunking:
| Error | Stage |
|---|---|
Invalid rerank model: … |
node was dropped by the hostname filter (this report) |
No credentials for local provider: <prefix> |
node was admitted and prefix matched; the node simply has no key stored |
No credentials for provider: <registry id> |
built-in registry route; unrelated to provider nodes |
Related
- PR #13732 (
RERANK_REMOTE_PROVIDER_NODES) — addresses exactly this, but is stillopen,mergeable_state: dirty(conflicts withrelease/v3.8.51), 5 of 16 CI checks red (API Route Typecheck,Fast Quality Gates, 3 of 4 unit-test shards), with #13733 stacked on top of it; the flag does not appear anywhere inrelease/v3.8.51. I am filing this as an issue because #13732 is a PR with no tracking issue, and because its stated motivation is LAN/Tailscale self-hosted nodes — this report adds the public SaaS / region-specific endpoint use case, which is a different affected population with the same root cause. - #9096 — fixed the apiType gate on the same filter; hostname gate left behind
- #6925 — fixed the embeddings analogue (auth-mode instead of admission)
- #5332 — registry coverage for Qwen3-Reranker (different: registry, not nodes)
Additional notes
- The rerank node path hardcodes
authType: "apikey"/authHeader: "bearer"and always sendsAuthorization: Bearer <token>. Unlike embeddings there is no no-auth branch for private hosts. /v1/rerankdoes not consult combos at all (the route never queries them), so rerank failover cannot be expressed as a combo — worth documenting separately, since operators who set up embedding combos naturally assume the same works for rerank.
Source: diegosouzapw/OmniRoute