security: Fix critical vulnerabilities and harden application

## Security Fixes

### CRITICAL: Command Injection (adminBackup.js)
- Replaced exec() with spawn() using argument arrays
- Added input sanitization for host, user, and ssh_key
- Added regex validation for hostname/IP format
- Added username format validation
- Added SSH key file existence check
- Prevents shell metacharacter injection attacks

### HIGH: Hardcoded Password (set-admin-password.js)
- Removed hardcoded 'admin123' password
- Now requires password as CLI argument or env variable
- Added password strength validation (8+ chars, mixed case, numbers, special chars)
- Added --help flag with usage instructions
- Invalidates existing sessions on password change

### MEDIUM: XSS Vulnerability (WelcomeMessageEditor.tsx)
- Added DOMPurify sanitization to getPreviewHtml()
- Strips all HTML tags before rendering preview
- Prevents script injection in admin preview

### LOW: Sample Password Exposure (EmailConfigPage.tsx)
- Replaced plaintext sample password with masked placeholder
- Uses '••••••••' instead of realistic password

## Dependency Updates
- Fixed npm audit vulnerabilities (jws, qs, express)
- Backend: 0 vulnerabilities
- Frontend: 0 vulnerabilities
This commit is contained in:
Paul Nothaft
2026-01-03 10:12:01 +01:00
parent 0da45e699a
commit f3c2cee362
5 changed files with 218 additions and 73 deletions
@@ -1,5 +1,6 @@
import React from 'react';
import { HelpCircle } from 'lucide-react';
import DOMPurify from 'dompurify';
interface WelcomeMessageEditorProps {
value: string;
@@ -18,9 +19,16 @@ export const WelcomeMessageEditor: React.FC<WelcomeMessageEditorProps> = ({
onChange(e.target.value);
};
// Convert newlines to <br> tags for preview
// Convert newlines to <br> tags for preview with XSS sanitization
const getPreviewHtml = () => {
return value
// First sanitize the input to remove any malicious content
const sanitized = DOMPurify.sanitize(value, {
ALLOWED_TAGS: [], // Strip all HTML tags, only allow text
ALLOWED_ATTR: [],
KEEP_CONTENT: true
});
// Then convert newlines to <br> tags
return sanitized
.split('\n')
.map(line => line.trim())
.filter(line => line.length > 0)