# 9router: Unauthenticated LLM proxy access via /codex rewrite authorization bypass

9router's middleware checked the pre-rewrite path, so unauthenticated `/codex/*` requests were rewritten to `/api/v1/responses` and used the operator's stored LLM provider keys.

- Payout: Research
- Severity: high
- Program: 9router
- Found by: dinhvaren
- Disclosed: 2026-08-28
- CVE: CVE-2026-55638
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-8gmq-j984-vp4r
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-8gmq-j984-vp4r
- Tags: area:ai domain:ai domain:web impact:internal-access kind:research platform:ghsa sev:high tech:llm tech:nextjs technique:source-review vuln:access-control vuln:auth-bypass

**Target:** Next.js LLM proxy middleware and rewrite config
**Root cause:** Middleware authorizes on the original incoming pathname against PUBLIC_PREFIXES ["/v1","/v1beta","/api/v1","/api/v1beta"], which omits `/codex`; the next.config.mjs rewrite then maps `/codex/:path*` to the protected `/api/v1/responses` route, which does not re-check the API key.
**Steps:**
1. Send `POST /api/v1/responses` with the LLM JSON body and no Authorization header; observe `401 Unauthorized`.
2. Send the identical body to `POST /codex/x` with no Authorization header; observe `200 OK`.
3. Observe the controlled upstream provider receiving `Authorization: Bearer NINEROUTER_OPERATOR_STORED_KEY_MARKER` with the request-body marker present.
4. Confirm isolation: `POST /notcodex/x` returns `404` and makes no upstream call.
5. Confirm the canonical path works with a valid key: `POST /api/v1/responses` with `Authorization: Bearer sk-REDACTED` returns `200 OK`.
**How found:** Compared the middleware's protected prefix list in src/dashboardGuard.js against the rewrite sources in next.config.mjs and noticed `/codex` was rewritten to a protected backend but absent from PUBLIC_PREFIXES; then diffed responses for canonical, rewritten, and unrelated paths to isolate the bypass.
**Exploitation:** Attacker finds a publicly reachable 9router instance and posts OpenAI/Anthropic-compatible chat requests to `/codex/*` without any API key. The middleware falls through to NextResponse.next(), the rewrite forwards to `/api/v1/responses`, handleChat runs, and the upstream provider call is made with the operator's stored credentials, consuming their quota and billing.
**Insight:** When a framework authorizes in middleware but routes via rewrites, enumerate every rewrite source and check it against the protected-prefix list — any source mapping into a gated destination but missing from the list is an auth bypass, and the backend handler often won't re-check.

