From 8b24b12ffbdb86da3bd06ede93947d86df585a66 Mon Sep 17 00:00:00 2001 From: Clippii Date: Sun, 28 Jun 2026 16:22:09 +0100 Subject: [PATCH] aa --- server.cjs | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/server.cjs b/server.cjs index 88ed662..03d50be 100644 --- a/server.cjs +++ b/server.cjs @@ -1281,6 +1281,31 @@ function isProtectedDataPath(req) { } } +function rawRequestPath(req) { + return String(req.url || '/').split(/[?#]/, 1)[0] || '/' +} + +function hasUnsafeProtectedPath(req) { + const rawPath = rawRequestPath(req) + const lower = rawPath.toLowerCase() + const protectedPrefix = + lower === '/api' || + lower.startsWith('/api/') || + lower.startsWith('/api%2f') || + lower.startsWith('/api%5c') || + lower === '/data' || + lower.startsWith('/data/') || + lower.startsWith('/data%2f') || + lower.startsWith('/data%5c') + + if (!protectedPrefix) return false + + const canonicalPrefix = rawPath.startsWith('/api/') || rawPath.startsWith('/data/') + if (!canonicalPrefix) return true + + return /%(?:00|2e|2f|5c)/i.test(rawPath) +} + function isSiteSessionBootstrapPath(req) { if (!req.url) return false try { @@ -2536,6 +2561,45 @@ ${urls.map((url) => ` }) } +function shouldBypassSpaFallback(requestPath) { + const lower = requestPath.toLowerCase() + const basename = path.posix.basename(lower) + const ext = path.posix.extname(lower) + + if (lower.startsWith('/.')) return true + if (lower.includes('/.git/') || lower.includes('/.svn/') || lower.includes('/.hg/')) return true + if (lower.startsWith('/api') || lower.startsWith('/data')) return true + + if ( + [ + '.cjs', + '.mjs', + '.js', + '.jsx', + '.ts', + '.tsx', + '.json', + '.lock', + '.toml', + '.yaml', + '.yml', + '.env', + '.sqlite', + '.db', + '.sql', + '.zip', + '.gz', + '.tgz', + '.tar', + '.bak', + ].includes(ext) + ) { + return true + } + + return /(?:^|[._-])(?:env|backup|dump|secret|credential|token|database|sqlite|db)(?:$|[._-])/.test(basename) +} + function serveStatic(req, res) { let requestPath = '/' try { @@ -2553,6 +2617,13 @@ function serveStatic(req, res) { fs.stat(filePath, (error, stat) => { if (error || !stat.isFile()) { + if (shouldBypassSpaFallback(requestPath)) { + return send(res, 404, 'Not found', { + 'content-type': 'text/plain; charset=utf-8', + 'cache-control': 'no-store', + }) + } + fs.readFile(path.join(DIST_DIR, 'index.html'), (indexError, indexData) => { if (indexError) { return send(res, 404, 'Build not found. Run npm run build first.', { @@ -2865,6 +2936,11 @@ function serveReplayMinimap(req, res) { } const server = http.createServer((req, res) => { + if (hasUnsafeProtectedPath(req)) { + sendJson(res, 404, { error: 'API route not found' }) + return + } + if (req.method === 'GET' && req.url === '/robots.txt') { sendRobotsTxt(req, res) return