{"id":"CVE-2026-56675","aliases":["EUVD-2026-42932","GHSA-x5c9-v98j-722r"],"status":"published","title":"9router: Reverse proxy locality collapse allows unauthenticated access to 9router /v1 APIs","description":"9Router is an AI router \u0026 token saver. Prior to 0.5.2, 9router treats loopback requests as trusted and allows /v1/* access without an API key, so a same-host reverse proxy that forwards public traffic to the backend through 127.0.0.1 causes src/dashboardGuard.js to misclassify external requests as local. A remote unauthenticated attacker can access /v1 APIs such as /v1/models and may abuse configured upstream provider credentials through /v1 proxy endpoints depending on enabled providers. This issue is fixed in version 0.5.2.","severity":{"version":"3.1","score":8.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:L","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-12","score":0.00501,"percentile":0.43439,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-287","CWE-290","CWE-306","CWE-441"],"published":"2026-07-10T15:39:23Z","modified":"2026-10-28T02:31:04Z","fixes":[{"ecosystem":"npm","package":"9router","fixed_in":["0.5.2"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"9router","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"0.4.80"},{"fixed":"0.5.2"}]}]},{"source":"osv","ecosystem":"npm","package":"9router","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"0.5.2"}]}]}],"products":[{"vendor":"decolua","product":"9router"}],"references":[{"url":"https://github.com/decolua/9router/security/advisories/GHSA-x5c9-v98j-722r","tags":["x_refsource_CONFIRM","advisory","web"]},{"url":"https://github.com/decolua/9router/commit/da667836cc7584bea0edd893de1d590c9ea279dc","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/decolua/9router/releases/tag/v0.5.2","tags":["x_refsource_MISC","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-56675","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-x5c9-v98j-722r"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/56xxx/CVE-2026-56675.json","tags":["advisory"]},{"url":"https://github.com/decolua/9router","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-56675","title":"9router: Reverse proxy locality collapse allows unauthenticated access to 9router /v1 APIs","description":"9Router is an AI router \u0026 token saver. Prior to 0.5.2, 9router treats loopback requests as trusted and allows /v1/* access without an API key, so a same-host reverse proxy that forwards public traffic to the backend through 127.0.0.1 causes src/dashboardGuard.js to misclassify external requests as local. A remote unauthenticated attacker can access /v1 APIs such as /v1/models and may abuse configured upstream provider credentials through /v1 proxy endpoints depending on enabled providers. This issue is fixed in version 0.5.2.","status":"published","published":"2026-07-10T15:39:23.282Z","modified":"2026-07-10T16:55:03.329Z","cvss":[{"version":"3.1","score":8.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:L","level":"high","assessor":"cna"}],"cwes":["CWE-287","CWE-290","CWE-306","CWE-441"],"updated_at":"2026-11-02T13:36:51.6229Z"}],"euvd":[{"id":"EUVD-2026-42932","description":"9Router is an AI router \u0026 token saver. Prior to 0.5.2, 9router treats loopback requests as trusted and allows /v1/* access without an API key, so a same-host reverse proxy that forwards public traffic to the backend through 127.0.0.1 causes src/dashboardGuard.js to misclassify external requests as local. A remote unauthenticated attacker can access /v1 APIs such as /v1/models and may abuse configured upstream provider credentials through /v1 proxy endpoints depending on enabled providers. This issue is fixed in version 0.5.2.","status":"published","published":"2026-07-10T15:39:23Z","modified":"2026-07-10T16:55:03Z","cvss":[{"version":"3.1","score":8.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:L","level":"high"}],"updated_at":"2026-11-02T18:20:02.822464Z"}],"ghsa":[{"id":"GHSA-x5c9-v98j-722r","title":"9router /v1 APIs has unauthenticated access via reverse proxy locality collapse","description":"## Summary\n\n9router treats local loopback requests as trusted and allows access to `/v1/*` without an\nAPI key. In a documented/common reverse-proxy deployment where nginx forwards public\ntraffic to the backend via `127.0.0.1`, external non-`Origin` requests are misclassified as\nlocal. This allows unauthenticated access to `/v1` APIs such as `/v1/models`, and may allow\nabuse of configured upstream provider credentials depending on the enabled providers.\n\n## Details\n\n- **Affected version / commit:** 9router `v0.4.80` @ `b282f05`.\n- **Deployment precondition:** a same-host reverse proxy (e.g. nginx) forwarding public\n  traffic to the backend on `127.0.0.1` / `localhost`. This mirrors the documented cloud\n  deployment (`proxy_pass http://localhost:20128` with `X-Real-IP` / `X-Forwarded-For`).\n- **Observed behaviour:**\n  - The **direct backend** (`direct-backend`, port `18081`) returns `401` for `/v1/models`\n    without an API key.\n  - A **direct request that spoofs** `X-9r-Real-IP: 127.0.0.1` still returns `401`: the\n    custom server deletes the client-supplied header and overwrites it with the real socket\n    address, so naive header spoofing does not work against the direct backend.\n  - The **proxied path** (`reverse-proxy`, port `18080`) returns `200` with the full model\n    catalog for the same `/v1/models` request **without any API key**.\n  - A **proxied request that carries an `Origin` header** returns `401`. The bypass\n    therefore primarily affects curl / SDK / server-side / non-browser clients, which do\n    not send `Origin`.\n- **Root cause:** the backend's local/remote decision relies on perceived socket/loopback\n  locality after reverse proxying. Because nginx connects to the backend from `127.0.0.1`,\n  the backend stamps a loopback client address for **every** internet client and treats the\n  request as local, skipping the `/v1` API-key requirement. The forwarded `X-Real-IP` /\n  `X-Forwarded-For` headers that carry the true client IP are ignored for this decision.\n- This is **not** a simple client header-spoofing issue (the direct-spoof control above\n  proves header spoofing is rejected); it is a property of how loopback proxy traffic is\n  trusted.\n\n## Proof of Concept\n\nThis repository is a self-contained Docker Compose reproduction. No real provider is called\nand no real API key is required.\n\n1. Build and start the stack:\n   ```bash\n   docker compose up --build\n   ```\n2. Direct baseline (no API key):\n   ```bash\n   curl -i http://127.0.0.1:18081/v1/models\n   ```\n3. Direct spoof control:\n   ```bash\n   curl -i -H \"X-9r-Real-IP: 127.0.0.1\" http://127.0.0.1:18081/v1/models\n   ```\n4. Reverse-proxy bypass (no API key):\n   ```bash\n   curl -i http://127.0.0.1:18080/v1/models\n   ```\n5. Reverse-proxy `Origin` control:\n   ```bash\n   curl -i -H \"Origin: http://evil.example\" http://127.0.0.1:18080/v1/models\n   ```\n\n### Expected evidence\n\n| Request | Result |\n|---------|--------|\n| Direct `18081`, no key | `401 Unauthorized` (`{\"error\":\"API key required for remote API access\"}`) |\n| Direct `18081`, `X-9r-Real-IP: 127.0.0.1` spoof | `401 Unauthorized` |\n| Proxied `18080`, no key | `200 OK` with the full model catalog |\n| Proxied `18080`, with `Origin` | `401 Unauthorized` |\n\n## Impact\n\n- Unauthenticated access to the `/v1` API surface in the affected reverse-proxy deployment.\n- Model enumeration via `/v1/models`.\n- Possible abuse of the operator's configured upstream provider credentials through\n  `/v1/chat/completions` and other `/v1` proxy endpoints (the attacker spends the operator's\n  provider quota/keys without holding any key of their own).\n- Actual impact depends on which providers are configured and how the instance is exposed\n  to the public internet.\n- The attacker requires **no API key**.\n\n## Suggested Fix\n\n- Do not use client/proxy/socket IP locality as an authentication bypass.\n- Require an API key by default for `/v1/*` on public listeners.\n- If local trust is genuinely needed, bind it to an unguessable server-generated secret or\n  to a Unix domain socket that is only accessible locally — not to \"the connection looks\n  like loopback\".\n- When running behind reverse proxies, use an explicit trusted-proxy configuration and a\n  real client-IP derivation (e.g. a vetted `X-Forwarded-For` chain), and never treat all\n  loopback proxy traffic as end-user-local.\n- Document a secure reverse-proxy configuration for operators.","status":"published","published":"2026-09-23T18:12:26Z","modified":"2026-09-23T18:12:27Z","cvss":[{"version":"3.1","score":8.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:L","level":"high"}],"cwes":["CWE-287","CWE-290","CWE-306","CWE-441"],"updated_at":"2026-11-02T14:41:20.575159Z"}],"nvd":[{"id":"CVE-2026-56675","description":"9Router is an AI router \u0026 token saver. Prior to 0.5.2, 9router treats loopback requests as trusted and allows /v1/* access without an API key, so a same-host reverse proxy that forwards public traffic to the backend through 127.0.0.1 causes src/dashboardGuard.js to misclassify external requests as local. A remote unauthenticated attacker can access /v1 APIs such as /v1/models and may abuse configured upstream provider credentials through /v1 proxy endpoints depending on enabled providers. This issue is fixed in version 0.5.2.","status":"published","published":"2026-07-10T17:17:01.07Z","modified":"2026-10-25T15:10:00.147Z","cvss":[{"version":"3.1","score":8.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:L","level":"high","assessor":"secondary"}],"cwes":["CWE-287","CWE-290","CWE-306","CWE-441"],"updated_at":"2026-11-02T14:12:28.771746Z"}],"osv":[{"id":"CVE-2026-56675","title":"9router: Reverse proxy locality collapse allows unauthenticated access to 9router /v1 APIs","description":"9Router is an AI router \u0026 token saver. Prior to 0.5.2, 9router treats loopback requests as trusted and allows /v1/* access without an API key, so a same-host reverse proxy that forwards public traffic to the backend through 127.0.0.1 causes src/dashboardGuard.js to misclassify external requests as local. A remote unauthenticated attacker can access /v1 APIs such as /v1/models and may abuse configured upstream provider credentials through /v1 proxy endpoints depending on enabled providers. This issue is fixed in version 0.5.2.","status":"published","published":"2026-07-10T15:39:23.282Z","modified":"2026-10-28T02:31:04.999258167Z","cvss":[{"version":"3.1","score":8.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:L","level":"high"}],"cwes":["CWE-287","CWE-290","CWE-306","CWE-441"],"updated_at":"2026-11-02T15:45:11.609219Z"},{"id":"GHSA-x5c9-v98j-722r","title":"9router /v1 APIs has unauthenticated access via reverse proxy locality collapse","description":"## Summary\n\n9router treats local loopback requests as trusted and allows access to `/v1/*` without an\nAPI key. In a documented/common reverse-proxy deployment where nginx forwards public\ntraffic to the backend via `127.0.0.1`, external non-`Origin` requests are misclassified as\nlocal. This allows unauthenticated access to `/v1` APIs such as `/v1/models`, and may allow\nabuse of configured upstream provider credentials depending on the enabled providers.\n\n## Details\n\n- **Affected version / commit:** 9router `v0.4.80` @ `b282f05`.\n- **Deployment precondition:** a same-host reverse proxy (e.g. nginx) forwarding public\n  traffic to the backend on `127.0.0.1` / `localhost`. This mirrors the documented cloud\n  deployment (`proxy_pass http://localhost:20128` with `X-Real-IP` / `X-Forwarded-For`).\n- **Observed behaviour:**\n  - The **direct backend** (`direct-backend`, port `18081`) returns `401` for `/v1/models`\n    without an API key.\n  - A **direct request that spoofs** `X-9r-Real-IP: 127.0.0.1` still returns `401`: the\n    custom server deletes the client-supplied header and overwrites it with the real socket\n    address, so naive header spoofing does not work against the direct backend.\n  - The **proxied path** (`reverse-proxy`, port `18080`) returns `200` with the full model\n    catalog for the same `/v1/models` request **without any API key**.\n  - A **proxied request that carries an `Origin` header** returns `401`. The bypass\n    therefore primarily affects curl / SDK / server-side / non-browser clients, which do\n    not send `Origin`.\n- **Root cause:** the backend's local/remote decision relies on perceived socket/loopback\n  locality after reverse proxying. Because nginx connects to the backend from `127.0.0.1`,\n  the backend stamps a loopback client address for **every** internet client and treats the\n  request as local, skipping the `/v1` API-key requirement. The forwarded `X-Real-IP` /\n  `X-Forwarded-For` headers that carry the true client IP are ignored for this decision.\n- This is **not** a simple client header-spoofing issue (the direct-spoof control above\n  proves header spoofing is rejected); it is a property of how loopback proxy traffic is\n  trusted.\n\n## Proof of Concept\n\nThis repository is a self-contained Docker Compose reproduction. No real provider is called\nand no real API key is required.\n\n1. Build and start the stack:\n   ```bash\n   docker compose up --build\n   ```\n2. Direct baseline (no API key):\n   ```bash\n   curl -i http://127.0.0.1:18081/v1/models\n   ```\n3. Direct spoof control:\n   ```bash\n   curl -i -H \"X-9r-Real-IP: 127.0.0.1\" http://127.0.0.1:18081/v1/models\n   ```\n4. Reverse-proxy bypass (no API key):\n   ```bash\n   curl -i http://127.0.0.1:18080/v1/models\n   ```\n5. Reverse-proxy `Origin` control:\n   ```bash\n   curl -i -H \"Origin: http://evil.example\" http://127.0.0.1:18080/v1/models\n   ```\n\n### Expected evidence\n\n| Request | Result |\n|---------|--------|\n| Direct `18081`, no key | `401 Unauthorized` (`{\"error\":\"API key required for remote API access\"}`) |\n| Direct `18081`, `X-9r-Real-IP: 127.0.0.1` spoof | `401 Unauthorized` |\n| Proxied `18080`, no key | `200 OK` with the full model catalog |\n| Proxied `18080`, with `Origin` | `401 Unauthorized` |\n\n## Impact\n\n- Unauthenticated access to the `/v1` API surface in the affected reverse-proxy deployment.\n- Model enumeration via `/v1/models`.\n- Possible abuse of the operator's configured upstream provider credentials through\n  `/v1/chat/completions` and other `/v1` proxy endpoints (the attacker spends the operator's\n  provider quota/keys without holding any key of their own).\n- Actual impact depends on which providers are configured and how the instance is exposed\n  to the public internet.\n- The attacker requires **no API key**.\n\n## Suggested Fix\n\n- Do not use client/proxy/socket IP locality as an authentication bypass.\n- Require an API key by default for `/v1/*` on public listeners.\n- If local trust is genuinely needed, bind it to an unguessable server-generated secret or\n  to a Unix domain socket that is only accessible locally — not to \"the connection looks\n  like loopback\".\n- When running behind reverse proxies, use an explicit trusted-proxy configuration and a\n  real client-IP derivation (e.g. a vetted `X-Forwarded-For` chain), and never treat all\n  loopback proxy traffic as end-user-local.\n- Document a secure reverse-proxy configuration for operators.","status":"published","published":"2026-09-23T18:12:26Z","modified":"2026-09-23T18:27:57.782900279Z","cvss":[{"version":"3.1","score":8.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:L","level":"high"}],"cwes":["CWE-287","CWE-290","CWE-306","CWE-441"],"updated_at":"2026-11-02T15:46:59.484835Z"}]}}
