From d37ba9edd48680b88b34689fb56af17903f728ed Mon Sep 17 00:00:00 2001 From: Ardis Lu Date: Thu, 21 May 2026 22:20:18 -0700 Subject: [PATCH] Verify `Origin` header on POST This is security hardening against cross-origin requests. Although the server already does not send CORS headers (so web browsers should already block a cross-origin POST), there should still be a server-side `Origin` check as defense-in-depth. --- src/getPrf.js | 7 ++++++- test/getPrf.test.js | 15 +++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/getPrf.js b/src/getPrf.js index 49786e8..3e39e36 100644 --- a/src/getPrf.js +++ b/src/getPrf.js @@ -87,8 +87,13 @@ export async function getPrf(options = {}) { .end(html); return; } - // Check if the payload has the correct challenge, and if so then use the prf + // Verify then use the prf else if (request.method === 'POST') { + const { port } = server.address(); + if (request.headers.origin !== `http://localhost:${port}`) { + response.writeHead(403).end(); + return; + } let body = ''; request.on('data', chunk => body += chunk); request.on('end', () => { diff --git a/test/getPrf.test.js b/test/getPrf.test.js index e899621..f593595 100644 --- a/test/getPrf.test.js +++ b/test/getPrf.test.js @@ -13,17 +13,20 @@ suite('getPrf.js (server)', () => { deepStrictEqual((await fetch(`${origin}?challenge=INVALID`)).status, 401); // Bad challenge deepStrictEqual((await fetch(url)).status, 200); // Good }); - test('validates payload on POST', async (t) => { + test('validates on POST', async (t) => { const { url, prf: prfActual } = await setupServer(t); const { origin, searchParams } = new URL(url); const challenge = searchParams.get('challenge'); const prf = 'deadbeef000000000000000000000000000000000000000000000000cafebabe'; - deepStrictEqual((await fetch(origin, { method: 'POST', body: JSON.stringify({ prf }) })).status, 401); // No challenge - deepStrictEqual((await fetch(origin, { method: 'POST', body: JSON.stringify({ challenge: 'INVALID', prf }) })).status, 401); // Bad challenge - deepStrictEqual((await fetch(origin, { method: 'POST', body: JSON.stringify({ challenge }) })).status, 400); // No prf - deepStrictEqual((await fetch(origin, { method: 'POST', body: JSON.stringify({ challenge, prf: 'INVALID' }) })).status, 400); // Bad prf - deepStrictEqual((await fetch(origin, { method: 'POST', body: JSON.stringify({ challenge, prf }) })).status, 200); // Good + deepStrictEqual((await fetch(origin, { method: 'POST', body: JSON.stringify({ challenge, prf }) })).status, 403); // No origin + deepStrictEqual((await fetch(origin, { method: 'POST', headers: { origin: 'null' }, body: JSON.stringify({ challenge, prf }) })).status, 403); // Null origin + deepStrictEqual((await fetch(origin, { method: 'POST', headers: { origin: 'https://example.com' }, body: JSON.stringify({ challenge, prf }) })).status, 403); // Bad origin + deepStrictEqual((await fetch(origin, { method: 'POST', headers: { origin }, body: JSON.stringify({ prf }) })).status, 401); // No challenge + deepStrictEqual((await fetch(origin, { method: 'POST', headers: { origin }, body: JSON.stringify({ challenge: 'INVALID', prf }) })).status, 401); // Bad challenge + deepStrictEqual((await fetch(origin, { method: 'POST', headers: { origin }, body: JSON.stringify({ challenge }) })).status, 400); // No prf + deepStrictEqual((await fetch(origin, { method: 'POST', headers: { origin }, body: JSON.stringify({ challenge, prf: 'INVALID' }) })).status, 400); // Bad prf + deepStrictEqual((await fetch(origin, { method: 'POST', headers: { origin }, body: JSON.stringify({ challenge, prf }) })).status, 200); // Good deepStrictEqual(await prfActual, Uint8Array.fromHex(prf)); }); test('window expiration works', async (t) => { -- 2.47.3