diff --git a/apps/demos/utils/server/csp-check.js b/apps/demos/utils/server/csp-check.js index d0928a8fb506..480b96a8c91b 100644 --- a/apps/demos/utils/server/csp-check.js +++ b/apps/demos/utils/server/csp-check.js @@ -9,7 +9,7 @@ const DEMO_ROOT = join(__dirname, '..', '..'); const REPORT_DIR = join(DEMO_ROOT, 'csp-reports'); const SERVER_URL = process.env.CSP_SERVER_URL || 'http://localhost:8080'; const FRAMEWORK = (process.env.CSP_FRAMEWORKS || 'jQuery').trim(); -const CONCURRENCY = parseInt(process.env.CSP_CONCURRENCY, 10) || 8; +const CONCURRENCY = parseInt(process.env.CSP_CONCURRENCY, 10) || 10; function findChrome() { const candidates = [ @@ -84,10 +84,16 @@ function visitPage(url) { '--disable-software-rasterizer', '--disable-dev-shm-usage', '--dump-dom', - '--virtual-time-budget=5000', + '--virtual-time-budget=2000', '--window-size=100,100', url, - ], { timeout: 30000 }, () => resolve()); + ], { timeout: 50000, killSignal: 'SIGKILL' }, (error) => { + if (error && error.killed) { + reject(new Error(`Chrome timed out for ${url}`)); + } else { + resolve(); + } + }); child.on('error', (err) => { reject(new Error(`Failed to launch Chrome at "${CHROME_PATH}": ${err.message}`)); }); @@ -112,6 +118,22 @@ function httpRequest(url, method) { }); } +async function runPool(items, concurrency, fn) { + let nextIndex = 0; + async function worker() { + while (nextIndex < items.length) { + const i = nextIndex; + nextIndex += 1; + await fn(items[i], i); + } + } + const workers = []; + for (let w = 0; w < Math.min(concurrency, items.length); w += 1) { + workers.push(worker()); + } + await Promise.all(workers); +} + async function main() { console.log(`Chrome: ${CHROME_PATH}`); console.log(`Server: ${SERVER_URL}`); @@ -130,48 +152,40 @@ async function main() { let demosWithViolations = 0; const allViolations = []; - for (let batchStart = 0; batchStart < demos.length; batchStart += CONCURRENCY) { - const batch = demos.slice(batchStart, batchStart + CONCURRENCY); + await httpRequest(`${SERVER_URL}/csp-violations`, 'DELETE'); - await httpRequest(`${SERVER_URL}/csp-violations`, 'DELETE'); + await runPool(demos, CONCURRENCY, async (demo, i) => { + const idx = i + 1; + const snapshot = await httpRequest(`${SERVER_URL}/csp-violations`); + const since = snapshot.lastId || 0; - await Promise.all(batch.map((demo) => visitPage(demo.url))); - - await new Promise((resolve) => { setTimeout(resolve, 500); }); - - const result = await httpRequest(`${SERVER_URL}/csp-violations`); - const violations = result.violations || []; - - const violationsByUrl = {}; - for (const v of violations) { - const uri = v.documentUri || ''; - if (!violationsByUrl[uri]) violationsByUrl[uri] = []; - violationsByUrl[uri].push(v); + try { + await visitPage(demo.url); + } catch (err) { + console.log(` ⚠️ [${idx}/${demos.length}] ${demo.widget}/${demo.demo}/${demo.framework} — ${err.message}`); + return; } - for (let j = 0; j < batch.length; j += 1) { - const demo = batch[j]; - const idx = batchStart + j + 1; - const demoViolations = violationsByUrl[demo.url] - || violationsByUrl[`${demo.url}index.html`] - || []; - - if (demoViolations.length > 0) { - demosWithViolations += 1; - totalViolations += demoViolations.length; - - console.log(` ❌ [${idx}/${demos.length}] ${demo.widget}/${demo.demo}/${demo.framework} — ${demoViolations.length} violation(s)`); - for (const v of demoViolations) { - const blocked = v.blockedUri || 'N/A'; - const directive = v.effectiveDirective || v.violatedDirective || '?'; - console.log(` ${directive}: ${blocked}`); - allViolations.push({ ...v, framework: FRAMEWORK }); - } - } else { - console.log(` ✅ [${idx}/${demos.length}] ${demo.widget}/${demo.demo}/${demo.framework}`); + const result = await httpRequest(`${SERVER_URL}/csp-violations?since=${since}`); + const violations = (result.violations || []).filter( + (v) => v.documentUri === demo.url || v.documentUri === `${demo.url}index.html`, + ); + + if (violations.length > 0) { + demosWithViolations += 1; + totalViolations += violations.length; + + console.log(` ❌ [${idx}/${demos.length}] ${demo.widget}/${demo.demo}/${demo.framework} — ${violations.length} violation(s)`); + for (const v of violations) { + const blocked = v.blockedUri || 'N/A'; + const directive = v.effectiveDirective || v.violatedDirective || '?'; + console.log(` ${directive}: ${blocked}`); + allViolations.push({ ...v, framework: FRAMEWORK }); } + } else { + console.log(` ✅ [${idx}/${demos.length}] ${demo.widget}/${demo.demo}/${demo.framework}`); } - } + }); const reportFile = join(REPORT_DIR, `csp-violations-${FRAMEWORK.toLowerCase()}.jsonl`); diff --git a/apps/demos/utils/server/csp-server.js b/apps/demos/utils/server/csp-server.js index 38d40a9ba8df..014cf7f49a68 100644 --- a/apps/demos/utils/server/csp-server.js +++ b/apps/demos/utils/server/csp-server.js @@ -2,7 +2,6 @@ const crypto = require('crypto'); const express = require('express'); -const serveStatic = require('serve-static'); const cookieParser = require('cookie-parser'); const { join, resolve } = require('path'); const { readFileSync, readdirSync } = require('fs'); @@ -376,30 +375,9 @@ const demoIndexHandler = (request, response) => { response.send(fileContent); }; -const createRateLimiter = (windowMs = 60000, maxRequests = 200) => { - const hits = new Map(); - - setInterval(() => hits.clear(), windowMs); - - return (req, res, next) => { - const key = req.ip; - const count = (hits.get(key) || 0) + 1; - hits.set(key, count); - - if (count > maxRequests) { - res.status(429).send('Too Many Requests'); - return; - } - next(); - }; -}; - -const rateLimiter = createRateLimiter(); - const app = express(); app.use(cookieParser()); app.use(cspMiddleware); -app.use(rateLimiter); app.post('/csp-report', cspReportHandler); app.get('/csp-violations', cspViolationsHandler); @@ -408,9 +386,7 @@ app.delete('/csp-violations', cspViolationsClearHandler); app.get('/apps/demos/Demos/:widget/:name/:approach', demoIndexHandler); app.get(`/apps/demos/Demos/:widget/:name/:approach/${indexFileName}`, demoIndexHandler); -app.use( - serveStatic(root, { index: [indexFileName] }), -); +app.use(express.static(root, { index: [indexFileName] })); const server = app.listen(port, host, () => { console.log(`CSP Demo server listening on http://${host}:${port}`);