Datenbank bereinigt / Gitea-Integration gefixt

Dieser Commit ist enthalten in:
hendrik_gebhardt@gmx.de
2026-01-04 00:24:11 +00:00
committet von Server Deploy
Ursprung 395598c2b0
Commit c21be47428
37 geänderte Dateien mit 30993 neuen und 809 gelöschten Zeilen

Datei anzeigen

@ -5,24 +5,52 @@
*/
const sanitizeHtml = require('sanitize-html');
const createDOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');
// DOMPurify für Server-side Rendering initialisieren
const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);
/**
* HTML-Tags entfernen (für reine Text-Felder)
* HTML-Entities dekodieren
*/
function stripHtml(input) {
if (typeof input !== 'string') return input;
return sanitizeHtml(input, {
allowedTags: [],
allowedAttributes: {}
}).trim();
function decodeHtmlEntities(str) {
if (typeof str !== 'string') return str;
const entities = {
'&': '&',
'&lt;': '<',
'&gt;': '>',
'&quot;': '"',
'&#039;': "'",
'&#x27;': "'",
'&apos;': "'"
};
return str.replace(/&(amp|lt|gt|quot|#039|#x27|apos);/g, match => entities[match] || match);
}
/**
* Markdown-sichere Bereinigung (erlaubt bestimmte Tags)
* HTML-Tags entfernen (für reine Text-Felder)
* Wichtig: sanitize-html encoded &-Zeichen zu &amp;, daher dekodieren wir danach
*/
function stripHtml(input) {
if (typeof input !== 'string') return input;
const sanitized = sanitizeHtml(input, {
allowedTags: [],
allowedAttributes: {}
}).trim();
// Entities wieder dekodieren, da sanitize-html sie encoded
return decodeHtmlEntities(sanitized);
}
/**
* Markdown-sichere Bereinigung mit DOMPurify (doppelte Sicherheit)
*/
function sanitizeMarkdown(input) {
if (typeof input !== 'string') return input;
return sanitizeHtml(input, {
// Erste Bereinigung mit sanitize-html
const firstPass = sanitizeHtml(input, {
allowedTags: [
'p', 'br', 'strong', 'em', 'u', 's', 'code', 'pre',
'ul', 'ol', 'li', 'blockquote', 'a', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
@ -44,6 +72,16 @@ function sanitizeMarkdown(input) {
}
}
});
// Zweite Bereinigung mit DOMPurify (zusätzliche Sicherheit)
return DOMPurify.sanitize(firstPass, {
ALLOWED_TAGS: [
'p', 'br', 'strong', 'em', 'u', 's', 'code', 'pre',
'ul', 'ol', 'li', 'blockquote', 'a', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
],
ALLOWED_ATTR: ['href', 'title', 'target', 'rel'],
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto):|[^a-z]|[a-z+.-]+(?:[^a-z+.-:]|$))/i
});
}
/**
@ -65,7 +103,15 @@ function sanitizeObject(obj, options = {}) {
for (const [key, value] of Object.entries(obj)) {
// Bestimmte Felder dürfen Markdown enthalten
const allowHtml = ['description', 'content'].includes(key);
sanitized[key] = sanitizeObject(value, { allowHtml });
// Passwort-Felder NICHT sanitizen (Sonderzeichen erhalten)
const skipSanitization = ['password', 'oldPassword', 'newPassword', 'confirmPassword'].includes(key);
if (skipSanitization) {
sanitized[key] = value; // Passwort unverändert lassen
} else {
sanitized[key] = sanitizeObject(value, { allowHtml });
}
}
return sanitized;
}
@ -119,12 +165,32 @@ const validators = {
},
/**
* URL-Format prüfen
* URL-Format prüfen (erweiterte Sicherheit)
*/
url: (value, fieldName) => {
try {
if (value) {
new URL(value);
const url = new URL(value);
// Nur HTTP/HTTPS erlauben
if (!['http:', 'https:'].includes(url.protocol)) {
return `${fieldName} muss HTTP oder HTTPS verwenden`;
}
// Localhost und private IPs blocken (SSRF-Schutz)
const hostname = url.hostname;
if (hostname === 'localhost' ||
hostname === '127.0.0.1' ||
hostname.startsWith('192.168.') ||
hostname.startsWith('10.') ||
hostname.startsWith('172.')) {
return `${fieldName} darf nicht auf lokale Adressen verweisen`;
}
// JavaScript URLs blocken
if (url.href.toLowerCase().startsWith('javascript:')) {
return `${fieldName} enthält ungültigen JavaScript-Code`;
}
}
return null;
} catch {