fix: enforce the signed-in user's role on cross-node proxied requests (#1505)

Proxied requests authenticated to a remote node previously ran as admin
regardless of the originating user's role, so a non-admin using the UI
against a remote node could reach admin-only handlers there.

The forwarding primary now asserts the user's role on a trusted header
that the remote honors only for node_proxy/pilot_tunnel bearers (the same
trust model as the license tier header), and the gateway overwrites the
header on every proxied request so a client cannot smuggle it. An absent
header keeps admin for direct instance-to-instance and background service
calls; an unrecognized role fails closed to read-only.
This commit is contained in:
Anso
2026-06-28 16:32:50 -04:00
committed by GitHub
parent d6ce60d280
commit 78a742fb44
5 changed files with 262 additions and 4 deletions
+10 -1
View File
@@ -1,7 +1,7 @@
import type { Request, Response, NextFunction, RequestHandler } from 'express';
import { createProxyMiddleware } from 'http-proxy-middleware';
import { NodeRegistry } from '../services/NodeRegistry';
import { PROXY_TIER_HEADER } from '../services/license-headers';
import { PROXY_TIER_HEADER, PROXY_ROLE_HEADER } from '../services/license-headers';
import { LicenseService } from '../services/LicenseService';
import { isProxyExemptPath } from '../helpers/proxyExemptPaths';
import { getErrorMessage } from '../utils/errors';
@@ -49,6 +49,15 @@ export function createRemoteProxyMiddleware(): RequestHandler {
// state changes within one proxy call.
const headers = LicenseService.getInstance().getProxyHeaders();
proxyReq.setHeader(PROXY_TIER_HEADER, headers.tier);
// Forward the signed-in user's role so the remote enforces their RBAC
// rather than treating every proxied request as admin. Strip first so a
// browser/API client cannot smuggle the header through the gateway, then
// re-set from the authenticated session (authGate runs before this proxy,
// so req.user is always resolved here).
proxyReq.removeHeader(PROXY_ROLE_HEADER);
if (req.user?.role) {
proxyReq.setHeader(PROXY_ROLE_HEADER, req.user.role);
}
// Strip the ?nodeId= query param so the remote's nodeContextMiddleware
// doesn't reject the request with 404 ("Node X not found") - the remote
// has no record of the gateway's node IDs and should treat the request