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 |
| AUTO_UPLOAD | Enable automatic upload on file selection | false | 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
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
allowedExtensions: process.env.ALLOWED_EXTENSIONS ?
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

View File

@@ -6,27 +6,32 @@
const { safeCompare } = require('../utils/security');
const logger = require('../utils/logger');
const { config } = require('../config');
/**
* Security headers middleware
*/
function securityHeaders(req, res, next) {
// Content Security Policy
res.setHeader(
'Content-Security-Policy',
let csp =
"default-src 'self'; " +
"style-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:;";
// X-Content-Type-Options
res.setHeader('X-Content-Type-Options', 'nosniff');
// X-Frame-Options
// If allowedIframeOrigins is set, allow those origins to embed via iframe
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');
}
// X-XSS-Protection
res.setHeader('Content-Security-Policy', csp);
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-XSS-Protection', '1; mode=block');
// Strict Transport Security (when in production)