From cf75d47053e260e42d6e204aa918d22aae09b766 Mon Sep 17 00:00:00 2001 From: Neil Smithline Date: Mon, 6 Apr 2026 16:35:01 -0400 Subject: [PATCH] Add CSRF protection to block cross-origin browser requests Reject requests with foreign Origin/Referer headers before any body parsing or permit signing, preventing malicious websites from triggering EIP-2612 permit signatures via cross-origin POST to localhost. Fixes ENG-1799 Co-Authored-By: Claude Opus 4.6 (1M context) --- proxy.ts | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/proxy.ts b/proxy.ts index db52af9..9d98f73 100644 --- a/proxy.ts +++ b/proxy.ts @@ -861,6 +861,32 @@ async function fetchStartupBalances(): Promise<{ return { rusd, sbc }; } +function validateCsrfProtection( + req: http.IncomingMessage, +): { ok: true } | { ok: false; reason: string } { + const origin = req.headers["origin"]; + const referer = req.headers["referer"]; + const allowed = [`http://localhost:${PORT}`, `http://127.0.0.1:${PORT}`]; + + // Browsers always send Origin on cross-origin requests. + // CLI/IDE clients typically omit it, so absent Origin is allowed. + if (origin && !allowed.some((a) => origin === a)) { + return { ok: false, reason: "Forbidden cross-origin request" }; + } + if (referer) { + try { + const refOrigin = new URL(referer).origin; + if (!allowed.some((a) => refOrigin === a)) { + return { ok: false, reason: "Forbidden cross-origin request" }; + } + } catch { + return { ok: false, reason: "Invalid Referer header" }; + } + } + + return { ok: true }; +} + function validateRequirement(requirement: PaymentRequirement): string | null { if (requirement.scheme !== EXPECTED_PAYMENT_SCHEME) { return `Unsupported scheme: ${requirement.scheme}. Expected ${EXPECTED_PAYMENT_SCHEME}`; @@ -2672,6 +2698,15 @@ const server = http.createServer(async (req, res) => { logStep("rewrite", `${path} -> ${upstreamPath}`); } + const csrf = validateCsrfProtection(req); + if (!csrf.ok) { + logStep("csrf", csrf.reason); + res.writeHead(403); + res.end(JSON.stringify({ error: csrf.reason })); + req.resume(); + return; + } + const chunks: Buffer[] = []; req.on("data", (chunk) => chunks.push(chunk));