mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-07-01 01:23:06 +08:00
fix(code-editor): harden media preview against SVG XSS and improve a11y
Withhold the open-in-new-tab action for SVG previews. The link is a top-level navigation to a blob URL, which inherits the app's origin, so a user-controlled SVG containing <script> would execute as same-origin script. Inline <img> rendering is unaffected and stays available. Also give the icon-only header actions (open-in-new-tab, fullscreen toggle, close) explicit aria-labels and mark their decorative SVG icons aria-hidden, so screen readers announce each action instead of relying on title alone.
This commit is contained in:
@@ -142,6 +142,13 @@ export default function CodeEditorMediaPreview({
|
||||
// stale URL from the previous file is never rendered during a switch.
|
||||
const currentUrl = url && loadedKey === sourceKey ? url : null;
|
||||
|
||||
// SVGs render safely inline via <img> (scripts don't execute there), but the
|
||||
// open-in-new-tab link is a top-level navigation. A blob URL inherits the
|
||||
// app's origin, so a user-controlled SVG with an embedded <script> would run
|
||||
// as same-origin script. Withhold the new-tab action for SVGs.
|
||||
const isSvg = getPreviewMimeType(file.name) === 'image/svg+xml';
|
||||
const canOpenInNewTab = Boolean(currentUrl) && !isSvg;
|
||||
|
||||
const renderMedia = () => {
|
||||
if (!currentUrl) return null;
|
||||
switch (kind) {
|
||||
@@ -198,15 +205,16 @@ export default function CodeEditorMediaPreview({
|
||||
|
||||
const headerActions = (
|
||||
<div className="flex shrink-0 items-center gap-0.5">
|
||||
{currentUrl && (
|
||||
{canOpenInNewTab && currentUrl && (
|
||||
<a
|
||||
href={currentUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center justify-center rounded-md p-1.5 text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white"
|
||||
aria-label={labels.openInNewTab}
|
||||
title={labels.openInNewTab}
|
||||
>
|
||||
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg aria-hidden="true" className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
||||
</svg>
|
||||
</a>
|
||||
@@ -216,14 +224,15 @@ export default function CodeEditorMediaPreview({
|
||||
type="button"
|
||||
onClick={onToggleFullscreen}
|
||||
className="flex items-center justify-center rounded-md p-1.5 text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white"
|
||||
aria-label={isFullscreen ? labels.exitFullscreen : labels.fullscreen}
|
||||
title={isFullscreen ? labels.exitFullscreen : labels.fullscreen}
|
||||
>
|
||||
{isFullscreen ? (
|
||||
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg aria-hidden="true" className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 9V4.5M9 9H4.5M9 9L3.5 3.5M9 15v4.5M9 15H4.5M9 15l-5.5 5.5M15 9h4.5M15 9V4.5M15 9l5.5-5.5M15 15h4.5M15 15v4.5m0-4.5l5.5 5.5" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg aria-hidden="true" className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" />
|
||||
</svg>
|
||||
)}
|
||||
@@ -233,9 +242,10 @@ export default function CodeEditorMediaPreview({
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="flex items-center justify-center rounded-md p-1.5 text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white"
|
||||
aria-label={labels.close}
|
||||
title={labels.close}
|
||||
>
|
||||
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg aria-hidden="true" className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user