fix: reject RPC requests without CSRF tokens
This commit is contained in:
@@ -268,7 +268,7 @@
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@wrnexus/cli",
|
||||
"version": "0.8.23",
|
||||
"version": "0.8.24",
|
||||
"bin": {
|
||||
"wrnexus": "src/index.ts",
|
||||
},
|
||||
@@ -332,7 +332,7 @@
|
||||
},
|
||||
"packages/dev-server": {
|
||||
"name": "@wrnexus/dev-server",
|
||||
"version": "0.8.22",
|
||||
"version": "0.8.23",
|
||||
"dependencies": {
|
||||
"@wrnexus/authz": "workspace:*",
|
||||
"@wrnexus/cache": "workspace:*",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/cli",
|
||||
"version": "0.8.23",
|
||||
"version": "0.8.24",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/dev-server",
|
||||
"version": "0.8.22",
|
||||
"version": "0.8.23",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -57,6 +57,18 @@ export { getWrnCompileMetrics, resetWrnCompileMetrics } from "./pipeline.ts";
|
||||
export type { WrnCompileMetrics } from "./pipeline.ts";
|
||||
import { resetDevCache } from "./cache.ts";
|
||||
|
||||
export function validateRpcCsrf(request: Request): boolean {
|
||||
const url = new URL(request.url);
|
||||
const origin = request.headers.get("origin");
|
||||
if (origin && origin !== url.origin) return false;
|
||||
const cookieHeader = request.headers.get("cookie") ?? "";
|
||||
const cookieToken =
|
||||
/(?:^|;\s*)wrn-csrf=([^;]+)/.exec(cookieHeader)?.[1] ??
|
||||
/(?:^|;\s*)wrnexus_csrf=([^;]+)/.exec(cookieHeader)?.[1];
|
||||
const headerToken = request.headers.get("x-csrf-token") ?? request.headers.get("x-wrnexus-csrf");
|
||||
return Boolean(cookieToken && headerToken && decodeURIComponent(cookieToken) === headerToken);
|
||||
}
|
||||
|
||||
import type { DevToolbarConfig } from "@wrnexus/dev-toolbar/types";
|
||||
import { createPluginRunner, discoverPlugins, type PluginInput } from "@wrnexus/plugin";
|
||||
import type { ObservabilityConfig, TenancyConfig } from "@wrnexus/styles";
|
||||
@@ -561,18 +573,7 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
|
||||
}
|
||||
return null;
|
||||
},
|
||||
validateCsrf(request) {
|
||||
const url = new URL(request.url);
|
||||
const origin = request.headers.get("origin");
|
||||
if (origin && origin !== url.origin) return false;
|
||||
const cookieHeader = request.headers.get("cookie") ?? "";
|
||||
const cookieToken =
|
||||
/(?:^|;\s*)wrn-csrf=([^;]+)/.exec(cookieHeader)?.[1] ??
|
||||
/(?:^|;\s*)wrnexus_csrf=([^;]+)/.exec(cookieHeader)?.[1];
|
||||
const headerToken =
|
||||
request.headers.get("x-csrf-token") ?? request.headers.get("x-wrnexus-csrf") ?? "";
|
||||
return !cookieToken || decodeURIComponent(cookieToken) === headerToken;
|
||||
},
|
||||
validateCsrf: validateRpcCsrf,
|
||||
});
|
||||
|
||||
const server = Bun.serve<WsData>({
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { validateRpcCsrf } from "../src/index.ts";
|
||||
|
||||
function request(headers: Record<string, string> = {}): Request {
|
||||
return new Request("https://app.example/__wrnexus/rpc", {
|
||||
method: "POST",
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
test("RPC CSRF requires a matching cookie and header from the same origin", () => {
|
||||
expect(validateRpcCsrf(request())).toBe(false);
|
||||
expect(validateRpcCsrf(request({ cookie: "wrn-csrf=token" }))).toBe(false);
|
||||
expect(validateRpcCsrf(request({ "x-csrf-token": "token" }))).toBe(false);
|
||||
expect(validateRpcCsrf(request({ cookie: "wrn-csrf=token", "x-csrf-token": "forged" }))).toBe(
|
||||
false,
|
||||
);
|
||||
expect(
|
||||
validateRpcCsrf(
|
||||
request({
|
||||
cookie: "wrn-csrf=token",
|
||||
"x-csrf-token": "token",
|
||||
origin: "https://evil.example",
|
||||
}),
|
||||
),
|
||||
).toBe(false);
|
||||
expect(
|
||||
validateRpcCsrf(
|
||||
request({
|
||||
cookie: "wrn-csrf=encoded%20token",
|
||||
"x-csrf-token": "encoded token",
|
||||
origin: "https://app.example",
|
||||
}),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
Reference in New Issue
Block a user