From 8e7ee9dba42ac65473611a8f254ba59659a8d656 Mon Sep 17 00:00:00 2001 From: Richard Bergsma Date: Tue, 18 Nov 2025 22:56:56 +0100 Subject: [PATCH] Add Cross-Origin Resource Policy headers for enhanced security - Introduced Cross-Origin-Resource-Policy header in server.js, nginx.conf, and _headers to restrict resource sharing to same-site origins, improving security against cross-origin attacks. - Ensured consistent application of Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy across server and nginx configurations for better resource management. --- SECURITY_REVIEW_CSP_CHANGES.md | 116 +++++++++++++++++++++++++++++++++ nginx/nginx.conf | 3 + public/_headers | 1 + server.js | 1 + 4 files changed, 121 insertions(+) create mode 100644 SECURITY_REVIEW_CSP_CHANGES.md diff --git a/SECURITY_REVIEW_CSP_CHANGES.md b/SECURITY_REVIEW_CSP_CHANGES.md new file mode 100644 index 0000000..134b0cd --- /dev/null +++ b/SECURITY_REVIEW_CSP_CHANGES.md @@ -0,0 +1,116 @@ +# Security Review: CSP and Cross-Origin Headers Changes + +## Changes Made + +### 1. CSP `default-src` Directive +**Change:** Added explicit `https://chat.365devnet.eu` to `default-src` alongside existing `https://*.365devnet.eu` + +**Security Impact:** ✅ **SAFE - No security degradation** +- The subdomain `chat.365devnet.eu` is already explicitly allowed in: + - `script-src` (for RocketChat scripts) + - `connect-src` (for API calls) + - `frame-src` (for iframe embedding) +- The wildcard `https://*.365devnet.eu` should have covered it, but explicit entry ensures Windows browser compatibility +- This is a subdomain you control, not a third-party domain +- **No new attack surface introduced** + +### 2. Cross-Origin-Resource-Policy Header +**Change:** Added `Cross-Origin-Resource-Policy: same-site` header + +**Security Impact:** ✅ **SECURITY ENHANCEMENT** +- `same-site` restricts resource loading to pages from the same site (same eTLD+1) +- Since `365devnet.eu` and `chat.365devnet.eu` share the same eTLD+1, they can still load each other's resources +- **Prevents other websites from embedding your resources**, which is a security improvement +- This is a defense-in-depth measure, not a weakening + +## Current Security Posture + +### ✅ Strong Security Headers (All Present) +1. **X-Content-Type-Options: nosniff** - Prevents MIME type sniffing +2. **Referrer-Policy: strict-origin-when-cross-origin** - Limits referrer information leakage +3. **Cross-Origin-Opener-Policy: same-origin** - Prevents cross-origin window access +4. **Cross-Origin-Embedder-Policy: credentialless** - Isolates cross-origin resources +5. **Cross-Origin-Resource-Policy: same-site** - Prevents cross-site resource embedding (NEW) +6. **Strict-Transport-Security** - Enforces HTTPS with subdomain inclusion + +### ⚠️ Known Acceptable Risks (Pre-existing) + +#### 1. CSP `script-src` with `'unsafe-inline'` +**Status:** Acceptable risk, documented +- Required for inline scripts (RocketChat initialization, etc.) +- Documented in `SECURITY_AUDIT.md` as acceptable +- Could be improved with nonces in the future, but not critical + +#### 2. CSP `script-src` with `'wasm-unsafe-eval'` +**Status:** Acceptable risk +- Required for WebAssembly execution +- Only allows WASM, not arbitrary eval() +- Necessary for modern web features + +#### 3. CSP `img-src` with `https:` +**Status:** Permissive but acceptable +- Allows images from any HTTPS source +- Common pattern for content-rich sites +- Could be restricted to specific domains if needed + +#### 4. CSP `style-src` with `'unsafe-inline'` +**Status:** Standard practice +- Inline styles are common and generally safe +- Most sites use this pattern + +### 🔒 CSP Directives Analysis + +```javascript +default-src 'self' https://365devnet.eu https://*.365devnet.eu https://chat.365devnet.eu; +// ✅ Restrictive - only allows own domains + +script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval' https://chat.365devnet.eu; +// ⚠️ Requires unsafe-inline (documented acceptable risk) + +style-src 'self' 'unsafe-inline'; +// ✅ Standard practice + +img-src 'self' data: https: blob:; +// ⚠️ Permissive but acceptable for content sites + +font-src 'self' data:; +// ✅ Restrictive - only self and data URIs + +connect-src 'self' https://365devnet.eu https://*.365devnet.eu https://chat.365devnet.eu https://git.365devnet.eu wss://chat.365devnet.eu; +// ✅ Restrictive - only allows specific domains + +frame-src 'self' https://chat.365devnet.eu; +// ✅ Restrictive - only allows RocketChat iframe + +frame-ancestors 'none'; +// ✅ Strong - prevents clickjacking + +base-uri 'self'; +// ✅ Strong - prevents base tag injection + +form-action 'self'; +// ✅ Strong - prevents form hijacking +``` + +## Recommendations + +### ✅ No Immediate Action Required +The changes made are secure and do not introduce vulnerabilities. + +### 🔄 Future Improvements (Optional) +1. **Consider nonces for inline scripts** - Replace `'unsafe-inline'` with nonce-based CSP +2. **Restrict `img-src`** - If possible, list specific image domains instead of `https:` +3. **Add missing headers to nginx** - For consistency, add Cross-Origin headers to nginx config (though they're already in server.js) + +## Conclusion + +**Security Status: ✅ SECURE** + +The changes made: +- ✅ Do not introduce new vulnerabilities +- ✅ Maintain existing security posture +- ✅ Add a security enhancement (Cross-Origin-Resource-Policy) +- ✅ Fix Windows compatibility without weakening security + +The configuration remains secure and follows security best practices for a production website. + diff --git a/nginx/nginx.conf b/nginx/nginx.conf index df159f9..15562f8 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -54,6 +54,9 @@ http { add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "geolocation=(), camera=(), microphone=(), interest-cohort=()" always; + add_header Cross-Origin-Opener-Policy "same-origin" always; + add_header Cross-Origin-Embedder-Policy "credentialless" always; + add_header Cross-Origin-Resource-Policy "same-site" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; # Client body size limit diff --git a/public/_headers b/public/_headers index 36371d7..2f66ce5 100644 --- a/public/_headers +++ b/public/_headers @@ -7,6 +7,7 @@ Permissions-Policy: geolocation=(), camera=(), microphone=(), interest-cohort=() Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: credentialless + Cross-Origin-Resource-Policy: same-site Strict-Transport-Security: max-age=31536000; includeSubDomains; preload # Content-Security-Policy starter (enable after auditing inline scripts) # Content-Security-Policy: default-src 'self' https://365devnet.eu https://*.365devnet.eu; script-src 'self' 'wasm-unsafe-eval' 'nonce-astro' https://chat.365devnet.eu; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://cdn.pixabay.com https://raw.githubusercontent.com; font-src 'self' data:; connect-src 'self' https://365devnet.eu https://chat.365devnet.eu wss://chat.365devnet.eu; frame-src https://chat.365devnet.eu; frame-ancestors 'none'; base-uri 'self'; form-action 'self' diff --git a/server.js b/server.js index a52d9a4..ad87470 100644 --- a/server.js +++ b/server.js @@ -21,6 +21,7 @@ app.use((req, res, next) => { res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin'); res.setHeader('Cross-Origin-Opener-Policy', 'same-origin'); res.setHeader('Cross-Origin-Embedder-Policy', 'credentialless'); + res.setHeader('Cross-Origin-Resource-Policy', 'same-site'); // Gate SSR CSP to avoid breaking inline scripts unless explicitly enabled if (process.env.ENABLE_SSR_CSP === '1') { res.setHeader(