API-key routes are served on a plaintext admin-granted listener, so the key crosses in clear #71

Open
opened 2026-10-27 09:43:50 +00:00 by james.oates · 0 comments
Owner

Same class as #69, which closed the dashboard login. Every route behind auth_middleware is authenticated by an X-API-Key header or a session cookie, and all of them are served on a plaintext listener wherever [[admin.access]] grants one. The redirect does not help: it lives in proxy_handler, the app's fallback, so it never runs for a matched route.

So a deployment that grants its admin host a plain binding is accepting its API key in cleartext on /api/services, /api/l4, /api/status, /api/logs and the rest — including the mutating POST/PATCH/DELETE routes.

Not started — this one has a wider blast radius than #69 and needs a decision.

Why it is not just "do what #69 did"

Refusing non-TLS on the auth_middleware routers would break loopback-only automation: a script on the host calling http://127.0.0.1/api/services with a key, which is a legitimate pattern and is arguably not an exposure at all, since the traffic never leaves the machine. A blanket refusal kills it; so would moving the redirect app-wide (#70).

Options, roughly:

  1. Refuse non-TLS on these routes as #69 does, and accept that loopback automation must move to HTTPS or to a Unix socket.
  2. Refuse non-TLS except from a loopback peer, treating "never left the host" as equivalent to TLS. Needs care: the peer must be the real TCP peer, not a forwarded value, and trust_forwarded_for must not be able to launder it.
  3. Make it configurable per [[admin]] entry, defaulting to refuse.
  4. Leave it to the access rules, and document that granting a plaintext listener to an admin host means accepting the key in clear.

Option 2 is probably right but is the one with a real chance of a subtle hole, which is why this wants its own change rather than riding along with #69.

  • #69 — the login, fixed by refusal.
  • #70 — the app-wide redirect, blocked on the liveness exemption.
  • #55 — the mutating registration routes sit outside admin_ip_middleware entirely, which is a different gap in the same area.
  • access::request_is_https already exists as the single trust-gated TLS test.
Same class as #69, which closed the dashboard login. Every route behind `auth_middleware` is authenticated by an `X-API-Key` header or a session cookie, and all of them are served on a plaintext listener wherever `[[admin.access]]` grants one. The redirect does not help: it lives in `proxy_handler`, the app's fallback, so it never runs for a matched route. So a deployment that grants its admin host a plain binding is accepting its API key in cleartext on `/api/services`, `/api/l4`, `/api/status`, `/api/logs` and the rest — including the mutating `POST`/`PATCH`/`DELETE` routes. **Not started — this one has a wider blast radius than #69 and needs a decision.** ## Why it is not just "do what #69 did" Refusing non-TLS on the `auth_middleware` routers would break loopback-only automation: a script on the host calling `http://127.0.0.1/api/services` with a key, which is a legitimate pattern and is arguably not an exposure at all, since the traffic never leaves the machine. A blanket refusal kills it; so would moving the redirect app-wide (#70). Options, roughly: 1. Refuse non-TLS on these routes as #69 does, and accept that loopback automation must move to HTTPS or to a Unix socket. 2. Refuse non-TLS *except* from a loopback peer, treating "never left the host" as equivalent to TLS. Needs care: the peer must be the real TCP peer, not a forwarded value, and `trust_forwarded_for` must not be able to launder it. 3. Make it configurable per `[[admin]]` entry, defaulting to refuse. 4. Leave it to the access rules, and document that granting a plaintext listener to an admin host means accepting the key in clear. Option 2 is probably right but is the one with a real chance of a subtle hole, which is why this wants its own change rather than riding along with #69. ## Related - #69 — the login, fixed by refusal. - #70 — the app-wide redirect, blocked on the liveness exemption. - #55 — the mutating registration routes sit outside `admin_ip_middleware` entirely, which is a different gap in the same area. - `access::request_is_https` already exists as the single trust-gated TLS test.
james.oates added this to the v2.1 milestone 2026-10-27 09:43:50 +00:00
Sign in to join this conversation.
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
IsoHex/edge-router#71
No description provided.