feat: Add ALLOWED_IFRAME_ORIGINS configuration and update security headers (#47) (#48)

- Introduced ALLOWED_IFRAME_ORIGINS environment variable to specify trusted origins for iframe embedding.
- Updated security headers middleware to conditionally allow specified origins in Content Security Policy.
- Enhanced documentation in README.md to explain the new configuration and its security implications.

Fixes #35
This commit is contained in:
Greirson Lee-Thorp
2025-05-02 17:25:27 -07:00
committed by GitHub
parent 12ae628bd4
commit 107684fe6a
3 changed files with 38 additions and 14 deletions

View File

@@ -123,6 +123,19 @@ docker run -p 3000:3000 -v "${PWD}\local_uploads:/app/uploads" dumbwareio/dumbdr
| APPRISE_SIZE_UNIT| Size unit for notifications | Auto | No | | APPRISE_SIZE_UNIT| Size unit for notifications | Auto | No |
| AUTO_UPLOAD | Enable automatic upload on file selection | false | No | | AUTO_UPLOAD | Enable automatic upload on file selection | false | No |
| ALLOWED_EXTENSIONS| Comma-separated list of allowed file extensions | None | No | | ALLOWED_EXTENSIONS| Comma-separated list of allowed file extensions | None | No |
| ALLOWED_IFRAME_ORIGINS | Comma-separated list of origins allowed to embed the app in an iframe (e.g. https://organizr.example.com,https://myportal.com) | None | No |
### ALLOWED_IFRAME_ORIGINS
To allow this app to be embedded in an iframe on specific origins (such as Organizr), set the `ALLOWED_IFRAME_ORIGINS` environment variable to a comma-separated list of allowed parent origins. Example:
```env
ALLOWED_IFRAME_ORIGINS=https://organizr.example.com,https://myportal.com
```
- If not set, the app will only allow itself to be embedded in an iframe on the same origin (default security).
- If set, the app will allow embedding in iframes on the specified origins and itself.
- **Security Note:** Only add trusted origins. Allowing arbitrary origins can expose your app to clickjacking and other attacks.
### File Extension Filtering ### File Extension Filtering
To restrict which file types can be uploaded, set the `ALLOWED_EXTENSIONS` environment variable. For example: To restrict which file types can be uploaded, set the `ALLOWED_EXTENSIONS` environment variable. For example:

View File

@@ -66,7 +66,13 @@ const config = {
// File extensions // File extensions
allowedExtensions: process.env.ALLOWED_EXTENSIONS ? allowedExtensions: process.env.ALLOWED_EXTENSIONS ?
process.env.ALLOWED_EXTENSIONS.split(',').map(ext => ext.trim().toLowerCase()) : process.env.ALLOWED_EXTENSIONS.split(',').map(ext => ext.trim().toLowerCase()) :
null null,
// Allowed iframe origins (for embedding in iframes)
// Comma-separated list of origins, e.g. "https://organizr.example.com,https://dumb.myportal.com"
allowedIframeOrigins: process.env.ALLOWED_IFRAME_ORIGINS
? process.env.ALLOWED_IFRAME_ORIGINS.split(',').map(origin => origin.trim()).filter(Boolean)
: null
}; };
// Validate required settings // Validate required settings

View File

@@ -6,34 +6,39 @@
const { safeCompare } = require('../utils/security'); const { safeCompare } = require('../utils/security');
const logger = require('../utils/logger'); const logger = require('../utils/logger');
const { config } = require('../config');
/** /**
* Security headers middleware * Security headers middleware
*/ */
function securityHeaders(req, res, next) { function securityHeaders(req, res, next) {
// Content Security Policy // Content Security Policy
res.setHeader( let csp =
'Content-Security-Policy',
"default-src 'self'; " + "default-src 'self'; " +
"style-src 'self' 'unsafe-inline' cdn.jsdelivr.net; " + "style-src 'self' 'unsafe-inline' cdn.jsdelivr.net; " +
"script-src 'self' 'unsafe-inline' cdn.jsdelivr.net; " + "script-src 'self' 'unsafe-inline' cdn.jsdelivr.net; " +
"img-src 'self' data: blob:;" "img-src 'self' data: blob:;";
);
// If allowedIframeOrigins is set, allow those origins to embed via iframe
// X-Content-Type-Options if (config.allowedIframeOrigins && config.allowedIframeOrigins.length > 0) {
// Remove X-Frame-Options header (do not set it)
// Add frame-ancestors directive to CSP
const frameAncestors = ["'self'", ...config.allowedIframeOrigins].join(' ');
csp += ` frame-ancestors ${frameAncestors};`;
} else {
// Default: only allow same origin if not configured
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
}
res.setHeader('Content-Security-Policy', csp);
res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('X-Content-Type-Options', 'nosniff');
// X-Frame-Options
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
// X-XSS-Protection
res.setHeader('X-XSS-Protection', '1; mode=block'); res.setHeader('X-XSS-Protection', '1; mode=block');
// Strict Transport Security (when in production) // Strict Transport Security (when in production)
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains'); res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
} }
next(); next();
} }