From a4d5461a8cb37dc55e19e15d8c2cf0fc632e4760 Mon Sep 17 00:00:00 2001 From: adamp Date: Tue, 10 Feb 2026 00:35:19 -0600 Subject: [PATCH] Whitelist SPA routes so unknown paths return 404 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the file-extension check with a regex whitelist of known client routes. Only whitelisted paths serve index.html for React Router — all other paths return a real 404. Co-Authored-By: Claude Opus 4.6 --- CHANGELOG.md | 2 +- server/index.js | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdbb7a7..f73a6ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ All notable changes to the Cookie Tracker are documented in this file. **Fix** (`server/index.js`): - Added a `/api` catch-all after all API route mounts that returns `404 JSON` (`{"error":"Not found"}`) for any unmatched API route. When auth is enabled, unauthenticated requests to non-existent API routes return 401 (the auth middleware intercepts before the 404 handler, avoiding route enumeration). -- Modified the SPA fallback to check `path.extname(req.path)` — requests with a file extension (e.g. `/foo.js`, `/missing.css`) now return a real 404 instead of `index.html`. Only extensionless navigation requests (e.g. `/orders`, `/customers/5`) serve `index.html` for React Router. +- Replaced the file-extension check with a regex whitelist of known client routes (`/`, `/inventory`, `/customers`, `/orders`, `/orders/new`, `/orders/:id`, `/restock`, `/reports`). Only these paths serve `index.html` for React Router. All other paths — extensionless (`/foo`), with special characters (`/it's`), or non-existent files (`/missing.js`) — return a real 404. --- diff --git a/server/index.js b/server/index.js index d27816b..1135262 100644 --- a/server/index.js +++ b/server/index.js @@ -52,13 +52,15 @@ app.use('/api', (req, res) => { if (process.env.NODE_ENV === 'production') { const clientDist = path.join(__dirname, '..', 'client', 'dist'); app.use(express.static(clientDist)); + + // Whitelist known client-side routes for SPA fallback. + // Everything else returns a real 404. + const spaRoutes = /^\/($|inventory$|customers$|orders($|\/new$|\/\d+$)|restock$|reports$)/; app.get('*', (req, res) => { - // Only serve index.html for navigation requests (no file extension). - // Requests for non-existent static files (e.g. /foo.js) get a real 404. - if (path.extname(req.path)) { - return res.status(404).end(); + if (spaRoutes.test(req.path)) { + return res.sendFile(path.join(clientDist, 'index.html')); } - res.sendFile(path.join(clientDist, 'index.html')); + res.status(404).end(); }); }