security cleanup, fix turnstile
This commit is contained in:
@@ -1,5 +1,42 @@
|
||||
import { onMount } from "solid-js";
|
||||
|
||||
/**
|
||||
* Sanitize Mermaid SVG output by removing dangerous elements and attributes.
|
||||
* Prevents stored XSS via malicious Mermaid diagram code.
|
||||
*/
|
||||
function sanitizeMermaidSvg(svgString: string): string {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(svgString, "text/html");
|
||||
|
||||
// Remove dangerous elements
|
||||
doc.querySelectorAll("script, iframe, object, embed, form, link, meta, base").forEach((el) => {
|
||||
el.remove();
|
||||
});
|
||||
|
||||
// Remove event handlers and dangerous attributes from all elements
|
||||
doc.querySelectorAll("[on*], [href*='javascript:'], [style*='expression(']").forEach((el) => {
|
||||
const attrs = Array.from(el.attributes);
|
||||
attrs.forEach((attr) => {
|
||||
if (
|
||||
attr.name.startsWith("on") ||
|
||||
attr.name === "href" ||
|
||||
attr.name === "style"
|
||||
) {
|
||||
const value = attr.value;
|
||||
if (
|
||||
attr.name.startsWith("on") ||
|
||||
value.includes("javascript:") ||
|
||||
value.includes("expression(")
|
||||
) {
|
||||
el.removeAttribute(attr.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return doc.body.innerHTML;
|
||||
}
|
||||
|
||||
export default function MermaidRenderer() {
|
||||
onMount(async () => {
|
||||
const mermaidPres = document.querySelectorAll('pre[data-type="mermaid"]');
|
||||
@@ -12,7 +49,7 @@ export default function MermaidRenderer() {
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
theme: "dark",
|
||||
securityLevel: "loose",
|
||||
securityLevel: "strict",
|
||||
fontFamily: "monospace",
|
||||
themeVariables: {
|
||||
darkMode: true,
|
||||
@@ -38,7 +75,7 @@ export default function MermaidRenderer() {
|
||||
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.className = "mermaid-rendered";
|
||||
wrapper.innerHTML = svg;
|
||||
wrapper.innerHTML = sanitizeMermaidSvg(svg);
|
||||
pre.replaceWith(wrapper);
|
||||
} catch (err) {
|
||||
console.error("Failed to render mermaid diagram:", err);
|
||||
|
||||
@@ -3,6 +3,46 @@ import type { HLJSApi } from "highlight.js";
|
||||
|
||||
const MermaidRenderer = lazy(() => import("./MermaidRenderer"));
|
||||
|
||||
/**
|
||||
* Sanitize HTML content to prevent XSS when rendering user-generated blog content.
|
||||
* Removes dangerous elements (script, iframe, object, etc.) and event handlers.
|
||||
*/
|
||||
function sanitizeHtml(html: string): string {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, "text/html");
|
||||
|
||||
// Remove dangerous elements
|
||||
doc
|
||||
.querySelectorAll(
|
||||
"script, iframe, object, embed, form, link, meta, base, svg script"
|
||||
)
|
||||
.forEach((el) => el.remove());
|
||||
|
||||
// Remove event handler attributes and dangerous URLs from all elements
|
||||
doc.querySelectorAll("[on*], [href], [style], [action]").forEach((el) => {
|
||||
const attrs = Array.from(el.attributes);
|
||||
attrs.forEach((attr) => {
|
||||
const name = attr.name;
|
||||
const value = attr.value;
|
||||
if (
|
||||
name.startsWith("on") ||
|
||||
(name === "href" &&
|
||||
(value.startsWith("javascript:") ||
|
||||
value.startsWith("data:text/html"))) ||
|
||||
(name === "style" &&
|
||||
(value.includes("expression(") ||
|
||||
value.includes("url(") ||
|
||||
value.includes("javascript:"))) ||
|
||||
(name === "action" && value.startsWith("javascript:"))
|
||||
) {
|
||||
el.removeAttribute(name);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return doc.body.innerHTML;
|
||||
}
|
||||
|
||||
export interface PostBodyClientProps {
|
||||
body: string;
|
||||
hasCodeBlock: boolean;
|
||||
@@ -21,10 +61,10 @@ export default function PostBodyClient(props: PostBodyClientProps) {
|
||||
const processCodeBlocks = () => {
|
||||
if (!contentRef) return;
|
||||
|
||||
const codeBlocks = contentRef.querySelectorAll("pre code");
|
||||
const codeBlocks = contentRef.querySelectorAll<HTMLElement>("pre code");
|
||||
|
||||
codeBlocks.forEach((codeBlock) => {
|
||||
const pre = codeBlock.parentElement;
|
||||
const pre = codeBlock.parentElement as HTMLPreElement | null;
|
||||
if (!pre) return;
|
||||
|
||||
if (pre.dataset.type === "mermaid") return;
|
||||
@@ -228,7 +268,7 @@ export default function PostBodyClient(props: PostBodyClientProps) {
|
||||
const referencesHeadingText =
|
||||
marker?.getAttribute("data-heading") || "References";
|
||||
|
||||
const headings = contentRef.querySelectorAll("h2");
|
||||
const headings = contentRef.querySelectorAll<HTMLElement>("h2");
|
||||
let referencesSection: HTMLElement | null = null;
|
||||
|
||||
headings.forEach((heading) => {
|
||||
@@ -401,7 +441,7 @@ export default function PostBodyClient(props: PostBodyClientProps) {
|
||||
id="post-content-body"
|
||||
ref={contentRef}
|
||||
class="text-text prose dark:prose-invert max-w-none"
|
||||
innerHTML={props.body}
|
||||
innerHTML={sanitizeHtml(props.body)}
|
||||
/>
|
||||
<Show when={props.hasMermaid}>
|
||||
<MermaidRenderer />
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { JSX, splitProps } from "solid-js";
|
||||
import { type JSX, splitProps } from "solid-js";
|
||||
|
||||
export interface InputProps extends JSX.InputHTMLAttributes<HTMLInputElement> {
|
||||
label?: string;
|
||||
error?: string;
|
||||
helperText?: string;
|
||||
ref?: HTMLInputElement | ((el: HTMLInputElement) => void);
|
||||
containerClass?: string;
|
||||
}
|
||||
|
||||
export default function Input(props: InputProps) {
|
||||
@@ -12,11 +13,16 @@ export default function Input(props: InputProps) {
|
||||
"label",
|
||||
"error",
|
||||
"helperText",
|
||||
"ref"
|
||||
"ref",
|
||||
"containerClass"
|
||||
]);
|
||||
|
||||
const containerClasses = ["input-group", local.containerClass]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
|
||||
return (
|
||||
<div class="input-group">
|
||||
<div class={containerClasses}>
|
||||
<input
|
||||
{...others}
|
||||
ref={local.ref}
|
||||
|
||||
Reference in New Issue
Block a user