diff --git a/services/core-web/runner/server.js b/services/core-web/runner/server.js index 3e27dd2dd6..0999ac7a6d 100755 --- a/services/core-web/runner/server.js +++ b/services/core-web/runner/server.js @@ -39,7 +39,22 @@ app.use( ? { directives: CONTENT_SECURITY_POLICY, } - : false, + : { + directives: { + defaultSrc: ["'self'"], + scriptSrc: ["'self'", "'unsafe-inline'"], // Note: unsafe-inline should be removed when proper nonces are implemented + styleSrc: ["'self'", "'unsafe-inline'"], // Note: unsafe-inline should be removed when proper nonces are implemented + imgSrc: ["'self'", "data:", "https:"], + connectSrc: ["'self'"], + fontSrc: ["'self'"], + objectSrc: ["'none'"], + mediaSrc: ["'self'"], + frameSrc: ["'none'"], + }, + }, + crossOriginEmbedderPolicy: { policy: "require-corp" }, + crossOriginOpenerPolicy: { policy: "same-origin" }, + crossOriginResourcePolicy: { policy: "same-origin" }, }) ); @@ -100,10 +115,45 @@ app.get(`/version`, (req, res) => { }); }); +// Explicit robots.txt endpoint with proper security headers +app.get('/robots.txt', (req, res) => { + res.setHeader('Content-Type', 'text/plain'); + res.setHeader('Cache-Control', 'public, max-age=86400'); + res.setHeader('Cross-Origin-Resource-Policy', 'same-origin'); + res.setHeader('X-Content-Type-Options', 'nosniff'); + res.send('User-agent: *\nDisallow: /'); +}); + +// Explicit sitemap.xml endpoint with proper security headers +app.get('/sitemap.xml', (req, res) => { + res.setHeader('Content-Type', 'application/xml'); + res.setHeader('Cache-Control', 'public, max-age=86400'); + res.setHeader('Cross-Origin-Resource-Policy', 'same-origin'); + res.setHeader('X-Content-Type-Options', 'nosniff'); + res.send('\n\n'); +}); + +// Handle trailing slash redirects for robots.txt and sitemap.xml +app.get('/robots.txt/', (req, res) => { + res.redirect(301, '/robots.txt'); +}); + +app.get('/sitemap.xml/', (req, res) => { + res.redirect(301, '/sitemap.xml'); +}); + app.use((req, res, next) => { if (PERMISSIONS_POLICY) { res.setHeader("Permissions-Policy", PERMISSIONS_POLICY); } + + // Additional security headers to address ZAP scan issues + res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp'); + res.setHeader('Cross-Origin-Opener-Policy', 'same-origin'); + res.setHeader('Cross-Origin-Resource-Policy', 'same-origin'); + res.setHeader('X-Content-Type-Options', 'nosniff'); + res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin'); + next(); }); diff --git a/services/minespace-web/runner/server.js b/services/minespace-web/runner/server.js index 77dfb86a41..59c3ce0fa3 100755 --- a/services/minespace-web/runner/server.js +++ b/services/minespace-web/runner/server.js @@ -38,7 +38,22 @@ app.use( ? { directives: CONTENT_SECURITY_POLICY, } - : false, + : { + directives: { + defaultSrc: ["'self'"], + scriptSrc: ["'self'", "'unsafe-inline'"], // Note: unsafe-inline should be removed when proper nonces are implemented + styleSrc: ["'self'", "'unsafe-inline'"], // Note: unsafe-inline should be removed when proper nonces are implemented + imgSrc: ["'self'", "data:", "https:"], + connectSrc: ["'self'"], + fontSrc: ["'self'"], + objectSrc: ["'none'"], + mediaSrc: ["'self'"], + frameSrc: ["'none'"], + }, + }, + crossOriginEmbedderPolicy: { policy: "require-corp" }, + crossOriginOpenerPolicy: { policy: "same-origin" }, + crossOriginResourcePolicy: { policy: "same-origin" }, }) ); @@ -89,10 +104,45 @@ app.get(`/version`, (req, res) => { }); }); +// Explicit robots.txt endpoint with proper security headers +app.get('/robots.txt', (req, res) => { + res.setHeader('Content-Type', 'text/plain'); + res.setHeader('Cache-Control', 'public, max-age=86400'); + res.setHeader('Cross-Origin-Resource-Policy', 'same-origin'); + res.setHeader('X-Content-Type-Options', 'nosniff'); + res.send('User-agent: *\nDisallow: /'); +}); + +// Explicit sitemap.xml endpoint with proper security headers +app.get('/sitemap.xml', (req, res) => { + res.setHeader('Content-Type', 'application/xml'); + res.setHeader('Cache-Control', 'public, max-age=86400'); + res.setHeader('Cross-Origin-Resource-Policy', 'same-origin'); + res.setHeader('X-Content-Type-Options', 'nosniff'); + res.send('\n\n'); +}); + +// Handle trailing slash redirects for robots.txt and sitemap.xml +app.get('/robots.txt/', (req, res) => { + res.redirect(301, '/robots.txt'); +}); + +app.get('/sitemap.xml/', (req, res) => { + res.redirect(301, '/sitemap.xml'); +}); + app.use((req, res, next) => { if (PERMISSIONS_POLICY) { res.setHeader("Permissions-Policy", PERMISSIONS_POLICY); } + + // Additional security headers to address ZAP scan issues + res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp'); + res.setHeader('Cross-Origin-Opener-Policy', 'same-origin'); + res.setHeader('Cross-Origin-Resource-Policy', 'same-origin'); + res.setHeader('X-Content-Type-Options', 'nosniff'); + res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin'); + next(); });