Enhance security and validation across multiple routes:
All checks were successful
Build & Push Docker Image / build (push) Successful in 6m25s

- Escape XML and HTML special characters to prevent injection attacks.
- Implement rate limiting for various endpoints to mitigate abuse.
- Add validation for email formats, password lengths, and field limits.
- Ensure proper access control for recordings and room management.
This commit is contained in:
2026-02-28 19:49:29 +01:00
parent 616442a82a
commit 7466f3513d
10 changed files with 398 additions and 47 deletions

View File

@@ -11,6 +11,13 @@ const __dirname = path.dirname(__filename);
const router = Router();
// Allowlist of valid theme IDs (keep in sync with src/themes/index.js)
const VALID_THEMES = new Set([
'light', 'dark', 'dracula', 'mocha', 'latte', 'nord', 'tokyo-night',
'gruvbox-dark', 'gruvbox-light', 'rose-pine', 'rose-pine-dawn',
'solarized-dark', 'solarized-light', 'one-dark', 'github-dark', 'scrunkly-cat',
]);
// Ensure uploads/branding directory exists
const brandingDir = path.join(__dirname, '..', '..', 'uploads', 'branding');
if (!fs.existsSync(brandingDir)) {
@@ -97,6 +104,15 @@ router.get('/logo', (req, res) => {
if (!logoFile) {
return res.status(404).json({ error: 'No logo found' });
}
// H5: serve SVG as attachment (Content-Disposition) to prevent in-browser script execution.
// For non-SVG images, inline display is fine.
const ext = path.extname(logoFile).toLowerCase();
if (ext === '.svg') {
res.setHeader('Content-Type', 'image/svg+xml');
res.setHeader('Content-Disposition', 'attachment; filename="logo.svg"');
res.setHeader('X-Content-Type-Options', 'nosniff');
return res.sendFile(logoFile);
}
res.sendFile(logoFile);
});
@@ -150,6 +166,9 @@ router.put('/name', authenticateToken, requireAdmin, async (req, res) => {
if (!appName || !appName.trim()) {
return res.status(400).json({ error: 'App name is required' });
}
if (appName.trim().length > 100) {
return res.status(400).json({ error: 'App name must not exceed 100 characters' });
}
await setSetting('app_name', appName.trim());
res.json({ appName: appName.trim() });
} catch (err) {
@@ -165,6 +184,10 @@ router.put('/default-theme', authenticateToken, requireAdmin, async (req, res) =
if (!defaultTheme || !defaultTheme.trim()) {
return res.status(400).json({ error: 'defaultTheme is required' });
}
// H4: validate against known theme IDs
if (!VALID_THEMES.has(defaultTheme.trim())) {
return res.status(400).json({ error: 'Invalid theme' });
}
await setSetting('default_theme', defaultTheme.trim());
res.json({ defaultTheme: defaultTheme.trim() });
} catch (err) {