fix(gateway): proxy browser server functions to workspace apps
Quality / quality (ubuntu-latest) (push) Failing after 10m40s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-18 23:30:42 +05:30
parent 701acd828c
commit 03d5cb6aa6
4 changed files with 63 additions and 16 deletions
+30 -2
View File
@@ -4,6 +4,7 @@ import {
defaultGatewayHostname,
forwardAuthFailure,
forwardAuthHeaders,
gatewayBrowserRpcHeaders,
gatewayProxyHeaders,
gatewayWebSocketBackendHeaders,
stripUntrustedInternalHeaders,
@@ -176,13 +177,40 @@ test("nested SSO proxy keeps the protected app's original request headers", () =
expect(proxied.get("x-original-uri")).toBe("/settings");
});
test("the reserved RPC prefix is refused at the gateway before any proxying", () => {
expect(isRpcGatewayPath(RPC_PATH_PREFIX)).toBe(true);
test("the gateway proxies browser server functions but refuses private RPC routes", () => {
expect(isRpcGatewayPath(RPC_PATH_PREFIX)).toBe(false);
expect(isRpcGatewayPath(`${RPC_PATH_PREFIX}/billing/createInvoice`)).toBe(true);
expect(isRpcGatewayPath("/api/billing")).toBe(false);
expect(isRpcGatewayPath("/__wrnexus/rpcfoo")).toBe(false);
});
test("browser RPC proxy preserves CSRF credentials and trusts only the internal hop", () => {
const request = new Request(`http://web.localhost:3000${RPC_PATH_PREFIX}`, {
method: "POST",
headers: {
host: "web.localhost:3000",
origin: "http://web.localhost:3000",
cookie: "wrn-csrf=token",
"x-csrf-token": "token",
[RPC_INTERNAL_HEADER]: "forged",
},
});
const headers = gatewayBrowserRpcHeaders(
request,
new URL(request.url),
"127.0.0.1",
true,
"http://127.0.0.1:3001",
);
expect(headers.get("origin")).toBe("http://127.0.0.1:3001");
expect(headers.get("cookie")).toBe("wrn-csrf=token");
expect(headers.get("x-csrf-token")).toBe("token");
expect(headers.get("x-forwarded-host")).toBe("web.localhost:3000");
expect(headers.has(RPC_INTERNAL_HEADER)).toBe(false);
expect(headers.has("host")).toBe(false);
});
test("an inbound internal-marker header from outside is stripped regardless of casing", () => {
for (const name of [
RPC_INTERNAL_HEADER,