Fix XSS in memory-browser-panel
Replace dangerouslySetInnerHTML with React elements for inline formatting (bold/italic). New renderInlineFormatting() helper returns React nodes instead of raw HTML strings, eliminating XSS risk from user-controlled memory content. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a4a606d5ac
commit
e6ec050ec3
|
|
@ -271,6 +271,30 @@ export function MemoryBrowserPanel() {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const renderInlineFormatting = (text: string): React.ReactNode[] => {
|
||||||
|
const parts: React.ReactNode[] = []
|
||||||
|
const regex = /(\*\*.*?\*\*|\*.*?\*)/g
|
||||||
|
let lastIndex = 0
|
||||||
|
let match: RegExpExecArray | null
|
||||||
|
let key = 0
|
||||||
|
while ((match = regex.exec(text)) !== null) {
|
||||||
|
if (match.index > lastIndex) {
|
||||||
|
parts.push(text.slice(lastIndex, match.index))
|
||||||
|
}
|
||||||
|
const m = match[0]
|
||||||
|
if (m.startsWith('**') && m.endsWith('**')) {
|
||||||
|
parts.push(<strong key={key++}>{m.slice(2, -2)}</strong>)
|
||||||
|
} else if (m.startsWith('*') && m.endsWith('*')) {
|
||||||
|
parts.push(<em key={key++}>{m.slice(1, -1)}</em>)
|
||||||
|
}
|
||||||
|
lastIndex = regex.lastIndex
|
||||||
|
}
|
||||||
|
if (lastIndex < text.length) {
|
||||||
|
parts.push(text.slice(lastIndex))
|
||||||
|
}
|
||||||
|
return parts
|
||||||
|
}
|
||||||
|
|
||||||
const renderMarkdown = (content: string) => {
|
const renderMarkdown = (content: string) => {
|
||||||
// Improved markdown rendering with proper line handling
|
// Improved markdown rendering with proper line handling
|
||||||
const lines = content.split('\n')
|
const lines = content.split('\n')
|
||||||
|
|
@ -323,20 +347,10 @@ export function MemoryBrowserPanel() {
|
||||||
elements.push(<div key={`${i}-space`} className="mb-2"></div>)
|
elements.push(<div key={`${i}-space`} className="mb-2"></div>)
|
||||||
} else if (trimmedLine.length > 0) {
|
} else if (trimmedLine.length > 0) {
|
||||||
if (inList) inList = false
|
if (inList) inList = false
|
||||||
// Handle inline formatting — escape HTML entities first to prevent XSS
|
|
||||||
let content = trimmedLine
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/"/g, '"')
|
|
||||||
.replace(/'/g, ''')
|
|
||||||
// Simple bold formatting
|
|
||||||
content = content.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
||||||
// Simple italic formatting
|
|
||||||
content = content.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
|
||||||
|
|
||||||
elements.push(
|
elements.push(
|
||||||
<p key={`${i}-p`} className="mb-2" dangerouslySetInnerHTML={{ __html: content }}></p>
|
<p key={`${i}-p`} className="mb-2">
|
||||||
|
{renderInlineFormatting(trimmedLine)}
|
||||||
|
</p>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue