mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-22 10:51:02 +01:00
fix: update Dropdown.svelte to properly close empty div tag and enhance markdown sanitizer
- Fixed an empty div tag in Dropdown.svelte to ensure proper HTML structure. - Implemented a custom HTML sanitizer in markdown.ts to avoid postcss dependency issues, allowing only specific tags and attributes to enhance security against XSS attacks.
This commit is contained in:
@@ -16,12 +16,14 @@
|
||||
"$utils/": "./src/lib/server/utils/",
|
||||
"$notifications/": "./src/lib/server/notifications/",
|
||||
"@std/assert": "jsr:@std/assert@^1.0.0",
|
||||
"marked": "npm:marked@^12.0.2",
|
||||
"sanitize-html": "npm:sanitize-html@^2.17.0",
|
||||
"marked": "npm:marked@^15.0.6",
|
||||
"simple-icons": "npm:simple-icons@^15.17.0"
|
||||
},
|
||||
"tasks": {
|
||||
"dev": "APP_BASE_PATH=./dist/dev vite dev",
|
||||
"dev": "APP_BASE_PATH=./dist/dev deno run -A npm:vite dev",
|
||||
"build": "APP_BASE_PATH=./dist/build deno run -A npm:vite build",
|
||||
"preview": "APP_BASE_PATH=./dist/data ./dist/build/profilarr",
|
||||
"compile": "deno compile --no-check --allow-net --allow-read --allow-write --allow-env --allow-ffi --allow-run --target x86_64-unknown-linux-gnu --output dist/build/profilarr dist/build/mod.ts",
|
||||
"format": "prettier --write .",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"test": "APP_BASE_PATH=./dist/test deno test src/tests --allow-read --allow-write --allow-env",
|
||||
|
||||
5
deno.lock
generated
5
deno.lock
generated
@@ -17,12 +17,10 @@
|
||||
"npm:eslint@^9.36.0": "9.39.1",
|
||||
"npm:globals@^16.4.0": "16.5.0",
|
||||
"npm:lucide-svelte@0.546": "0.546.0_svelte@5.43.3__acorn@8.15.0",
|
||||
"npm:marked@^12.0.2": "12.0.2",
|
||||
"npm:marked@^15.0.6": "15.0.12",
|
||||
"npm:prettier-plugin-svelte@^3.4.0": "3.4.0_prettier@3.6.2_svelte@5.43.3__acorn@8.15.0",
|
||||
"npm:prettier-plugin-tailwindcss@~0.6.14": "0.6.14_prettier@3.6.2_prettier-plugin-svelte@3.4.0__prettier@3.6.2__svelte@5.43.3___acorn@8.15.0_svelte@5.43.3__acorn@8.15.0",
|
||||
"npm:prettier@^3.6.2": "3.6.2",
|
||||
"npm:sanitize-html@^2.17.0": "2.17.0",
|
||||
"npm:simple-icons@^15.17.0": "15.17.0",
|
||||
"npm:svelte-check@^4.3.2": "4.3.3_svelte@5.43.3__acorn@8.15.0_typescript@5.9.3",
|
||||
"npm:svelte@^5.39.5": "5.43.3_acorn@8.15.0",
|
||||
@@ -1929,8 +1927,7 @@
|
||||
"workspace": {
|
||||
"dependencies": [
|
||||
"jsr:@std/assert@1",
|
||||
"npm:marked@^12.0.2",
|
||||
"npm:sanitize-html@^2.17.0",
|
||||
"npm:marked@^15.0.6",
|
||||
"npm:simple-icons@^15.17.0"
|
||||
],
|
||||
"packageJson": {
|
||||
|
||||
4731
package-lock.json
generated
Normal file
4731
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,7 @@
|
||||
|
||||
<!-- Dropdown content - can be used standalone or within a trigger wrapper -->
|
||||
<!-- Invisible hover bridge to keep dropdown open when moving mouse down -->
|
||||
<div class="absolute top-full z-40 h-3 w-full" />
|
||||
<div class="absolute top-full z-40 h-3 w-full"></div>
|
||||
|
||||
<div
|
||||
class="absolute top-full z-50 mt-3 rounded-lg border border-neutral-200 bg-white dark:border-neutral-700 dark:bg-neutral-800 {positionClass}"
|
||||
|
||||
@@ -3,7 +3,62 @@
|
||||
*/
|
||||
|
||||
import { marked } from 'marked';
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
|
||||
/**
|
||||
* Simple HTML sanitizer to avoid postcss dependency issues in compiled binaries
|
||||
*/
|
||||
function sanitizeHtml(html: string): string {
|
||||
// Allowed tags
|
||||
const allowedTags = new Set([
|
||||
'p', 'br', 'strong', 'em', 'u', 'code', 'pre', 'blockquote',
|
||||
'ul', 'ol', 'li', 'a', 'img', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
||||
'table', 'thead', 'tbody', 'tr', 'th', 'td', 'hr', 'del', 'ins'
|
||||
]);
|
||||
|
||||
// Allowed attributes per tag
|
||||
const allowedAttrs: Record<string, Set<string>> = {
|
||||
'a': new Set(['href', 'title']),
|
||||
'img': new Set(['src', 'alt', 'title'])
|
||||
};
|
||||
|
||||
// Remove script tags and their content
|
||||
html = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
|
||||
|
||||
// Remove event handlers and javascript: URLs
|
||||
html = html.replace(/\s*on\w+\s*=\s*["'][^"']*["']/gi, '');
|
||||
html = html.replace(/href\s*=\s*["']javascript:[^"']*["']/gi, '');
|
||||
|
||||
// Filter tags and attributes
|
||||
return html.replace(/<\/?([a-z][a-z0-9]*)\b([^>]*)>/gi, (match, tag, attrs) => {
|
||||
const lowerTag = tag.toLowerCase();
|
||||
|
||||
// Remove disallowed tags
|
||||
if (!allowedTags.has(lowerTag)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// If closing tag, allow it
|
||||
if (match.startsWith('</')) {
|
||||
return `</${lowerTag}>`;
|
||||
}
|
||||
|
||||
// Filter attributes
|
||||
const allowedForTag = allowedAttrs[lowerTag];
|
||||
if (!allowedForTag) {
|
||||
return `<${lowerTag}>`;
|
||||
}
|
||||
|
||||
const filteredAttrs = attrs.replace(/([a-z][a-z0-9-]*)\s*=\s*["']([^"']*)["']/gi,
|
||||
(attrMatch: string, attrName: string, attrValue: string) => {
|
||||
if (allowedForTag.has(attrName.toLowerCase())) {
|
||||
return ` ${attrName}="${attrValue}"`;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
return `<${lowerTag}${filteredAttrs}>`;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse markdown to sanitized HTML
|
||||
@@ -15,13 +70,7 @@ export function parseMarkdown(markdown: string | null | undefined): string {
|
||||
const html = marked.parse(markdown) as string;
|
||||
|
||||
// Sanitize HTML to prevent XSS
|
||||
return sanitizeHtml(html, {
|
||||
allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img', 'h1', 'h2']),
|
||||
allowedAttributes: {
|
||||
...sanitizeHtml.defaults.allowedAttributes,
|
||||
img: ['src', 'alt', 'title']
|
||||
}
|
||||
});
|
||||
return sanitizeHtml(html);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user