]> git.ardis.lu - envpass/commitdiff
Verify `Origin` header on POST
authorArdis Lu <ardisglu@gmail.com>
Fri, 22 May 2026 05:20:18 +0000 (22:20 -0700)
committerArdis Lu <ardisglu@gmail.com>
Fri, 22 May 2026 05:20:18 +0000 (22:20 -0700)
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
test/getPrf.test.js

index 49786e819b1d9747ee53f39910566b7504b37b88..3e39e36e71d369891fdb4542497a99fabbaeb703 100644 (file)
@@ -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', () => {
index e899621a1c4facbe568af4df778a1028ed009c93..f5935954729fb3a8a632684e0a46b7a293494443 100644 (file)
@@ -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) => {
This page took 0.036424 seconds and 4 git commands to generate.