Skip to content

Comments

Sanitize image url from DOM node#5779

Open
christopherholland-workday wants to merge 3 commits intomainfrom
dom-xss
Open

Sanitize image url from DOM node#5779
christopherholland-workday wants to merge 3 commits intomainfrom
dom-xss

Conversation

@christopherholland-workday
Copy link
Contributor

Sanitize the image URL to block potentially malicious content.

CodeQL finding 26.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @christopherholland-workday, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the application's security by implementing robust image URL sanitization. A new utility function has been added to validate image sources, ensuring that only safe http: and https: protocols are used. This prevents potential cross-site scripting (XSS) vulnerabilities that could arise from malicious or improperly formatted image URLs, thereby improving the overall integrity of the user interface.

Highlights

  • Image URL Sanitization: Introduced a sanitizeImageUrl utility function to validate image URLs, specifically preventing the use of potentially malicious schemes like javascript:, data:, or blob:.
  • Security Vulnerability Fix: Addressed CodeQL finding 26 by ensuring that only http: and https: protocols are permitted for image sources, defaulting to a generic avatar for invalid or disallowed URLs.
Changelog
  • packages/ui/src/views/assistants/openai/AssistantDialog.jsx
    • Implemented a new sanitizeImageUrl function to validate and clean image URLs.
    • Integrated the sanitizeImageUrl function into the src attribute of an img element to ensure all displayed images are from safe sources.
Activity
  • No specific activity (comments, reviews, progress) was provided in the context.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

}}
alt={assistantName}
src={assistantIcon}
src={sanitizeImageUrl(assistantIcon)}

Check failure

Code scanning / CodeQL

DOM text reinterpreted as HTML High

DOM text
is reinterpreted as HTML without escaping meta-characters.

Copilot Autofix

AI 3 days ago

In general, to fix this kind of issue you must ensure that untrusted text taken from the DOM is not reinterpreted as HTML or otherwise executed. For URLs, this means validating against a strict allowlist (e.g., schemes, hosts, and path patterns) and never returning raw user input unless it satisfies those constraints. For React, you should continue relying on React’s automatic attribute escaping and avoid any APIs that bypass it (like dangerouslySetInnerHTML).

Here, the best targeted fix is to strengthen sanitizeImageUrl so that it does not simply accept any HTTP(S) URL, but instead only allows URLs from the trusted avatar service already in use (DiceBear) and falls back to a known-safe default in all other cases. This both satisfies the static analysis by preventing arbitrary tainted data from flowing to the img src, and keeps the intended functionality (letting the user customize a DiceBear-based avatar) intact.

Concretely:

  • Edit the sanitizeImageUrl function (lines 128–145) in packages/ui/src/views/assistants/openai/AssistantDialog.jsx.
  • After parsing the URL, check that:
    • Protocol is http: or https: (as already done), and
    • Hostname is exactly api.dicebear.com, and
    • Path starts with /7.x/bottts/svg.
  • Only if all those checks pass, return the normalized URL string (e.g., parsed.toString() or parsed.href); otherwise, return the fallback URL.
  • Do not alter the rest of the component logic; assistantIcon can still be edited, but any non-DiceBear URL will render using the fallback, eliminating the tainted-flow concern.

No new imports are required; this uses only the standard URL class and existing variables.

Suggested changeset 1
packages/ui/src/views/assistants/openai/AssistantDialog.jsx

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/ui/src/views/assistants/openai/AssistantDialog.jsx b/packages/ui/src/views/assistants/openai/AssistantDialog.jsx
--- a/packages/ui/src/views/assistants/openai/AssistantDialog.jsx
+++ b/packages/ui/src/views/assistants/openai/AssistantDialog.jsx
@@ -125,7 +125,7 @@
     const customization = useSelector((state) => state.customization)
     const dialogRef = useRef()
 
-    // Sanitize image URL to prevent XSS attacks via javascript:, data:, or blob: schemes
+    // Sanitize image URL to prevent XSS attacks and limit to trusted avatar host
     const sanitizeImageUrl = (url) => {
         const fallbackUrl = `https://api.dicebear.com/7.x/bottts/svg?seed=fallback`
         if (!url || typeof url !== 'string') {
@@ -133,14 +133,18 @@
         }
         try {
             const parsed = new URL(url, window.location.origin)
-            // Only allow http and https protocols
-            if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {
-                return url
+            // Only allow http and https protocols and restrict to DiceBear bottts avatars
+            const isHttp = parsed.protocol === 'http:' || parsed.protocol === 'https:'
+            const isDiceBearHost = parsed.hostname === 'api.dicebear.com'
+            const isBotttsPath = parsed.pathname.startsWith('/7.x/bottts/svg')
+            if (isHttp && isDiceBearHost && isBotttsPath) {
+                // Return the normalized, safe URL string
+                return parsed.toString()
             }
         } catch (e) {
-            // Invalid URL
+            // Invalid URL, will fall back to default avatar
         }
-        // Return default avatar if URL is invalid or uses disallowed protocol
+        // Return default avatar if URL is invalid or uses disallowed host/path/protocol
         return fallbackUrl
     }
 
EOF
@@ -125,7 +125,7 @@
const customization = useSelector((state) => state.customization)
const dialogRef = useRef()

// Sanitize image URL to prevent XSS attacks via javascript:, data:, or blob: schemes
// Sanitize image URL to prevent XSS attacks and limit to trusted avatar host
const sanitizeImageUrl = (url) => {
const fallbackUrl = `https://api.dicebear.com/7.x/bottts/svg?seed=fallback`
if (!url || typeof url !== 'string') {
@@ -133,14 +133,18 @@
}
try {
const parsed = new URL(url, window.location.origin)
// Only allow http and https protocols
if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {
return url
// Only allow http and https protocols and restrict to DiceBear bottts avatars
const isHttp = parsed.protocol === 'http:' || parsed.protocol === 'https:'
const isDiceBearHost = parsed.hostname === 'api.dicebear.com'
const isBotttsPath = parsed.pathname.startsWith('/7.x/bottts/svg')
if (isHttp && isDiceBearHost && isBotttsPath) {
// Return the normalized, safe URL string
return parsed.toString()
}
} catch (e) {
// Invalid URL
// Invalid URL, will fall back to default avatar
}
// Return default avatar if URL is invalid or uses disallowed protocol
// Return default avatar if URL is invalid or uses disallowed host/path/protocol
return fallbackUrl
}

Copilot is powered by AI and may make mistakes. Always verify output.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a false positive, we sanitized the URL, but here, React removes the attribute values when passing in text like <img src={userInput} /> - it does not treat it as HTML content.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The pull request introduces a sanitizeImageUrl function to prevent XSS from malicious image URLs, which is a great security improvement. However, the implementation of the fallback logic within this function can cause performance issues and UI flickering due to the generation of a new UUID on every render for invalid URLs. I've suggested a fix to use a stable fallback URL.

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants