fix(artifacts): sanitize SVG content to prevent stored XSS#291
Open
sebastiondev wants to merge 1 commit into
Open
fix(artifacts): sanitize SVG content to prevent stored XSS#291sebastiondev wants to merge 1 commit into
sebastiondev wants to merge 1 commit into
Conversation
AI-generated SVG artifacts were rendered via dangerouslySetInnerHTML without any sanitization, allowing script execution through event handlers (e.g. onload) or embedded script tags. Add DOMPurify as a dependency and sanitize SVG content using its SVG profile before rendering. This strips dangerous elements and attributes while preserving valid SVG markup.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Vulnerability Summary
Stored Cross-Site Scripting (XSS) via unsanitized SVG artifact rendering — CWE-79
The
ArtifactRenderercomponent (frontend/src/components/chat/ArtifactRenderer.tsx) renders AI-generated SVG artifacts directly into the DOM usingdangerouslySetInnerHTMLwith no sanitization. The original code even carried abiome-ignore lint/security/noDangerouslySetInnerHtmlsuppression comment acknowledging the risk.Data flow
decodeHtmlEntities()(inMessageContentWithArtifacts.tsx), which decodes HTML entities — converting any encoded payloads back into executable markup.parseArtifacts()(inartifactParser.ts) extracts the artifact body verbatim via regex, assigning it tocontentwith only a.trim().ArtifactRendererreceives the parsed artifact and, fortype === 'svg', renders it directly:dangerouslySetInnerHTML={{ __html: artifact.content }}.No sanitization exists anywhere in this pipeline. An SVG payload containing
<script>,onloadevent handlers, or<foreignObject>with embedded HTML will execute in the viewer's browser session.Proof of Concept
An attacker who can influence AI output (e.g., via prompt injection in a shared conversation, a poisoned document in a knowledge base, or a malicious tool/webhook response) can cause the AI to produce a message containing:
When any user views the conversation containing this artifact, the JavaScript executes in their browser context. Since HUF is a multi-user platform with role-based access (Admin, Manager, User, Viewer) and cross-user conversation visibility, the stored XSS payload persists and impacts every subsequent viewer — enabling session hijacking, credential theft, or privilege escalation.
To reproduce locally:
onerrorhandler fires immediately when the<foreignObject>content renders.What this fix does
This PR adds DOMPurify-based sanitization for SVG content before it reaches
dangerouslySetInnerHTML:New utility (
frontend/src/utils/sanitize.ts): Exports asanitizeSVG()function that runs content throughDOMPurify.sanitize()withUSE_PROFILES: { svg: true, svgFilters: true }. This preserves valid SVG elements and attributes while stripping<script>,<foreignObject>, event handler attributes (onload,onerror, etc.), and other dangerous constructs.Updated renderer (
frontend/src/components/chat/ArtifactRenderer.tsx): The SVG case now callssanitizeSVG(artifact.content)instead of passingartifact.contentdirectly. The biome-ignore suppression comment is also removed since the sanitization addresses the underlying concern.New dependency (
frontend/package.json): Addsdompurify@^3.3.3, the industry-standard DOM sanitization library used by projects like Mozilla, WordPress, and Google.Why DOMPurify with SVG profiles
DOMPurify's SVG profile is specifically designed for this use case — it maintains a curated allowlist of safe SVG elements and attributes while blocking all known XSS vectors including
<foreignObject>script injection, SVG event handlers,xlink:hrefjavascript: URIs, and<animate>attribute clobbering. This is preferable to a custom regex-based approach, which historically fails to account for parser differentials and encoding edge cases.Testing
<script>,<foreignObject>with embedded HTML,onload/onerrorattributes) are stripped and do not execute.Adversarial review
Before submitting, we attempted to disprove this finding by checking whether any existing middleware, Content-Security-Policy headers, or framework-level protections would prevent exploitation. The application does not set CSP headers that would block inline script execution. The
decodeHtmlEntities()function reverses any entity encoding, andparseArtifacts()passes the body through without transformation. The Frappe/React architecture does not apply automatic output encoding at thedangerouslySetInnerHTMLboundary — that API exists specifically to bypass React's built-in XSS protections. No mitigation exists in the current pipeline.Submitted by Sebastion — autonomous open-source security research from Foundation Machines. Free for public repos via the Sebastion AI GitHub App.