Files
OmniRoute/scripts
Diego Rodrigues de Sa e Souza 0afd0d3dae fix(security): don't trust loopback socket as local when behind reverse proxy
When OmniRoute itself runs behind an external reverse proxy (nginx, Caddy,
Cloudflare Tunnel), `req.socket.remoteAddress` on the inbound connection is
the proxy hop -- usually 127.0.0.1 -- not the real end-user. The custom Node
server stamped that loopback IP as the trusted PEER_IP_HEADER, so
`classifyHostLocality()` returned "loopback" for every remote caller arriving
via the proxy. The LOCAL_ONLY route guard (which gates spawn-capable routes:
/api/mcp/, /api/cli-tools/runtime/, /api/services/, /api/plugins/,
/api/system/version, /api/tools/agent-bridge/, ...) was therefore
effectively bypassed: a leaked JWT over the public tunnel could trigger
arbitrary child-process spawning (Hard Rules #15 + #17).

Fix mirrors decolua/9router commit da667836:

  1. scripts/dev/peer-stamp.mjs stamps a companion VIA_PROXY_HEADER as
     `<token>|1` whenever the inbound request carries forwarding headers
     (`x-forwarded-for` / `x-real-ip`), token-protected with the same
     per-process secret as PEER_IP_HEADER so a remote caller cannot forge
     either its presence or its absence.

  2. src/server/authz/peerStamp.ts exposes a new
     `classifyStampedPeerLocality(peer, viaProxy, token)` that combines the
     two stamps: when the via-proxy marker is present, a loopback /
     private-LAN socket is downgraded to "remote" so the LOCAL_ONLY tier is
     not bypassed.

  3. src/server/authz/pipeline.ts switches the AUTHZ_HEADER_PEER_LOCALITY
     stamp to the new helper and strips VIA_PROXY_HEADER from the forwarded
     headers alongside PEER_IP_HEADER.

  4. src/server/authz/policies/management.ts gates `isLoopbackRequest()` and
     `isPrivateLanRequest()` on the same via-proxy check so the management
     policy carve-outs (CLI-token gate, LAN-allowed LOCAL_ONLY surface) also
     refuse to honour a proxy-hop socket.

TDD: tests/unit/route-guard-loopback-via-proxy.test.ts proves a request with
`socket.remoteAddress=127.0.0.1` + a via-proxy stamp is classified "remote".
RED first, then GREEN. The local-CLI happy path (loopback socket, no proxy
stamp) is unchanged. Untokened via-proxy hints from a remote attacker are
ignored. Existing tests (route-guard-private-lan, authz/routeGuard,
authz/pipeline, authz/management-policy) all stay green.

Inspired-by: decolua/9router@da667836
Co-authored-by: decolua <decoluadt@example.com>
2026-06-23 21:56:34 -03:00
..
2026-06-23 17:06:18 -03:00
2026-06-23 17:06:18 -03:00
2026-06-19 06:49:01 -03:00
2026-06-23 17:06:18 -03:00
2026-06-23 17:06:18 -03:00
2026-06-06 19:13:11 -03:00
2026-06-23 17:06:18 -03:00
2026-06-17 19:26:32 -03:00
2026-06-04 20:05:38 -03:00
2026-06-17 19:26:32 -03:00