🔒 fix(security): sanitize AI-generated HTML to prevent XSS in playground
Mitigate XSS vulnerabilities in the playground where AI-generated content is rendered without sanitization, allowing potential script injection via prompt injection attacks. MarkdownRenderer.jsx: - Replace dangerouslySetInnerHTML with a sandboxed iframe for HTML preview - Use sandbox="allow-same-origin" to block script execution while allowing CSS rendering and iframe height auto-sizing - Add SandboxedHtmlPreview component with automatic height adjustment CodeViewer.jsx: - Add escapeHtml() utility to encode HTML entities before rendering - Rewrite highlightJson() to process tokens iteratively, escaping each token and structural text before wrapping in syntax highlighting spans - Escape non-JSON and very-large content paths that previously bypassed sanitization - Update linkRegex to correctly match URLs containing & entities These changes only affect the playground (AI output rendering). Admin- configured content (home page, about page, footer, notices) remains unaffected as they use separate code paths and are within the trusted admin boundary.
This commit is contained in:
@@ -93,6 +93,49 @@ export function Mermaid(props) {
|
||||
);
|
||||
}
|
||||
|
||||
function SandboxedHtmlPreview({ code }) {
|
||||
const iframeRef = useRef(null);
|
||||
const [iframeHeight, setIframeHeight] = useState(150);
|
||||
|
||||
useEffect(() => {
|
||||
const iframe = iframeRef.current;
|
||||
if (!iframe) return;
|
||||
|
||||
const handleLoad = () => {
|
||||
try {
|
||||
const doc = iframe.contentDocument || iframe.contentWindow?.document;
|
||||
if (doc) {
|
||||
const height =
|
||||
doc.documentElement.scrollHeight || doc.body.scrollHeight;
|
||||
setIframeHeight(Math.min(Math.max(height + 16, 60), 600));
|
||||
}
|
||||
} catch {
|
||||
// sandbox restrictions may prevent access, that's fine
|
||||
}
|
||||
};
|
||||
|
||||
iframe.addEventListener('load', handleLoad);
|
||||
return () => iframe.removeEventListener('load', handleLoad);
|
||||
}, [code]);
|
||||
|
||||
return (
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
sandbox='allow-same-origin'
|
||||
srcDoc={code}
|
||||
title='HTML Preview'
|
||||
style={{
|
||||
width: '100%',
|
||||
height: `${iframeHeight}px`,
|
||||
border: 'none',
|
||||
overflow: 'auto',
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function PreCode(props) {
|
||||
const ref = useRef(null);
|
||||
const [mermaidCode, setMermaidCode] = useState('');
|
||||
@@ -227,7 +270,7 @@ export function PreCode(props) {
|
||||
>
|
||||
HTML预览:
|
||||
</div>
|
||||
<div dangerouslySetInnerHTML={{ __html: htmlCode }} />
|
||||
<SandboxedHtmlPreview code={htmlCode} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user