Skip to content

Commit f8ac591

Browse files
committed
fix(file-viewer): sanitize linked-image href; drop global leading-newline strip
- image: run the linked-image (badge) anchor target through normalizeLinkHref so a javascript:/data: href in a file can't execute on click; the markdown still preserves the raw target (file content unchanged) - markdown-fidelity: the table serializer now trims its own surrounding blank lines, so the global leading-newline strip in postProcessSerializedMarkdown is redundant — removing it stops clobbering content that legitimately begins with whitespace
1 parent f86f400 commit f8ac591

3 files changed

Lines changed: 17 additions & 10 deletions

File tree

apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/image.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { JSONContent } from '@tiptap/core'
33
import { Image } from '@tiptap/extension-image'
44
import type { ReactNodeViewProps } from '@tiptap/react'
55
import { NodeViewWrapper, ReactNodeViewRenderer } from '@tiptap/react'
6+
import { normalizeLinkHref } from './markdown-fidelity'
67

78
const MIN_WIDTH = 64
89

@@ -199,6 +200,10 @@ function ResizableImageView({ node, updateAttributes, selected }: ReactNodeViewP
199200
? { width: /^\d+$/.test(attrs.width) ? `${attrs.width}px` : attrs.width }
200201
: undefined
201202

203+
// Sanitize the linked-image target before rendering the anchor — a parsed markdown href is
204+
// untrusted and could be `javascript:`/`data:`; an unsafe value drops the link (image only).
205+
const safeHref = normalizeLinkHref(typeof attrs.href === 'string' ? attrs.href : '')
206+
202207
const image = (
203208
<img
204209
ref={imageRef}
@@ -217,8 +222,8 @@ function ResizableImageView({ node, updateAttributes, selected }: ReactNodeViewP
217222

218223
return (
219224
<NodeViewWrapper className='relative my-4 inline-block leading-none'>
220-
{attrs.href ? (
221-
<a href={attrs.href} rel='noopener noreferrer' className='block'>
225+
{safeHref ? (
226+
<a href={safeHref} rel='noopener noreferrer' className='block'>
222227
{image}
223228
</a>
224229
) : (

apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/markdown-fidelity.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,11 @@ export function normalizeLinkHref(href: string): string {
5151

5252
/**
5353
* Cleans up serializer output: restores callout markers the serializer backslash-escapes
54-
* (`> \[!NOTE\]` → `> [!NOTE]`) and trims the spurious leading blank line the table
55-
* serializer emits, plus trailing blank lines.
54+
* (`> \[!NOTE\]` → `> [!NOTE]`) and collapses trailing blank lines to a single newline. The
55+
* table serializer's spurious surrounding blank lines are trimmed at the source (PipeSafeTable),
56+
* so no global leading-newline strip is needed here — avoiding clobbering content that legitimately
57+
* begins with whitespace.
5658
*/
5759
export function postProcessSerializedMarkdown(markdown: string): string {
58-
return markdown
59-
.replace(ESCAPED_CALLOUT_REGEX, '$1[!$2]')
60-
.replace(/^\n+/, '')
61-
.replace(/\n+$/, '\n')
60+
return markdown.replace(ESCAPED_CALLOUT_REGEX, '$1[!$2]').replace(/\n+$/, '\n')
6261
}

apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/round-trip.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,11 @@ describe('markdown-fidelity utils', () => {
114114
expect(normalizeLinkHref('ftp://host/file')).toBe('ftp://host/file')
115115
})
116116

117-
it('trims a leading blank line and collapses trailing newlines', () => {
118-
expect(postProcessSerializedMarkdown('\n| a |\n| --- |\n\n\n')).toBe('| a |\n| --- |\n')
117+
it('collapses trailing blank lines and preserves leading whitespace', () => {
118+
expect(postProcessSerializedMarkdown('| a |\n| --- |\n\n\n')).toBe('| a |\n| --- |\n')
119+
// No global leading-newline strip (the table trims its own at the source), so content that
120+
// legitimately begins with a blank line is no longer clobbered on save.
121+
expect(postProcessSerializedMarkdown('\nbody\n')).toBe('\nbody\n')
119122
})
120123
})
121124

0 commit comments

Comments
 (0)