-
Notifications
You must be signed in to change notification settings - Fork 854
🔒 Security: Fix critical XSS vulnerability in Markdown renderer #914
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
🔒 Security: Fix critical XSS vulnerability in Markdown renderer #914
Conversation
SECURITY FIX - CVSS 9.6 (Critical) Problem: - rehype-raw plugin was rendering arbitrary HTML without sanitization - Attackers could inject malicious scripts via AI responses - Extension's high privileges (all_urls, cookies, storage) made XSS particularly dangerous - could steal API keys and user data Solution: - Added rehype-sanitize with strict schema configuration - Whitelist safe HTML tags and attributes only - Block dangerous elements: script, iframe, object, embed, form, etc. - Sanitization applied after rehype-raw to prevent XSS attacks Impact: - Protects all users from XSS injection via AI responses - Prevents API key theft and unauthorized actions - Maintains functionality while securing HTML rendering Files changed: - src/components/MarkdownRender/markdown.jsx - src/components/MarkdownRender/markdown-without-katex.jsx - package.json (added rehype-sanitize dependency) Related: SECURITY_AUDIT_REPORT.md VULN-001
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthroughAdds HTML sanitization to Markdown rendering: introduces Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
📜 Recent review detailsConfiguration used: defaults Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used📓 Path-based instructions (5)src/**/*.{js,jsx,mjs,ts,tsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
src/**/*.{js,jsx,mjs,css,scss}📄 CodeRabbit inference engine (AGENTS.md)
Files:
src/components/**/*.jsx📄 CodeRabbit inference engine (AGENTS.md)
Files:
src/**/*.jsx📄 CodeRabbit inference engine (AGENTS.md)
Files:
src/**/*.{js,mjs,jsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
🧬 Code graph analysis (1)src/components/MarkdownRender/markdown-without-katex.jsx (1)
🔇 Additional comments (2)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello @anuxs, 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 delivers an urgent security fix for a critical XSS vulnerability within the application's Markdown rendering component. Previously, the renderer allowed arbitrary HTML, posing a severe risk of data exfiltration and user compromise. The solution involves integrating a robust sanitization library, Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe 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
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 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
|
PR Compliance Guide 🔍Below is a summary of compliance checks for this PR:
Compliance status legend🟢 - Fully Compliant🟡 - Partial Compliant 🔴 - Not Compliant ⚪ - Requires Further Human Verification 🏷️ - Compliance label |
|||||||||||||||||||||||
PR Code Suggestions ✨Explore these optional code suggestions:
|
|||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This is an excellent and critical security fix. You've correctly identified the XSS vulnerability from using rehype-raw without sanitization and have properly mitigated it by adding rehype-sanitize to the pipeline. The detailed PR description is also very helpful. I've added a few suggestions to improve the maintainability of the sanitization schema by removing some redundancies and addressing code duplication. Overall, great work on securing the application.
| const sanitizeSchema = { | ||
| ...defaultSchema, | ||
| attributes: { | ||
| ...defaultSchema.attributes, | ||
| // Allow safe attributes | ||
| '*': ['className', 'id', 'dir'], | ||
| a: ['href', 'title', 'target', 'rel'], | ||
| img: ['src', 'alt', 'title', 'width', 'height'], | ||
| video: ['src', 'controls', 'width', 'height'], | ||
| code: ['className'], | ||
| div: ['className', 'id', 'dir'], | ||
| span: ['className', 'id'], | ||
| // Add custom element 'think' | ||
| think: [], | ||
| }, | ||
| tagNames: [ | ||
| ...(defaultSchema.tagNames || []), | ||
| 'think', // Add custom think tag | ||
| ].filter( | ||
| // Remove dangerous tags | ||
| (tag) => | ||
| !['script', 'iframe', 'object', 'embed', 'link', 'style', 'form', 'input', 'button'].includes( | ||
| tag, | ||
| ), | ||
| ), | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| const sanitizeSchema = { | ||
| ...defaultSchema, | ||
| attributes: { | ||
| ...defaultSchema.attributes, | ||
| // Allow safe attributes | ||
| '*': ['className', 'id', 'dir'], | ||
| a: ['href', 'title', 'target', 'rel'], | ||
| img: ['src', 'alt', 'title', 'width', 'height'], | ||
| video: ['src', 'controls', 'width', 'height'], | ||
| code: ['className'], | ||
| div: ['className', 'id', 'dir'], | ||
| span: ['className', 'id'], | ||
| // Add custom element 'think' | ||
| think: [], | ||
| }, | ||
| tagNames: [ | ||
| ...(defaultSchema.tagNames || []), | ||
| 'think', // Add custom think tag | ||
| ].filter( | ||
| // Remove dangerous tags | ||
| (tag) => | ||
| !['script', 'iframe', 'object', 'embed', 'link', 'style', 'form', 'input', 'button'].includes( | ||
| tag, | ||
| ), | ||
| ), | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sanitizeSchema can be simplified for better maintainability, and it's also duplicated in markdown-without-katex.jsx.
- Redundant attribute definitions: The
diventry is redundant as it's covered by the wildcard'*'. Thespanentry is a subset of the wildcard, which might be unintentional. - Redundant tag filtering: The
.filter()ontagNamesis redundant becauserehype-sanitize'sdefaultSchemais an allow-list that already excludes dangerous tags.
To improve maintainability and avoid duplication, I suggest simplifying the schema and extracting it to a shared file (e.g., src/components/MarkdownRender/sanitizeSchema.js) to be imported in both components.
const sanitizeSchema = {
...defaultSchema,
attributes: {
...defaultSchema.attributes,
// Allow safe attributes
'*': ['className', 'id', 'dir'],
a: ['href', 'title', 'target', 'rel'],
img: ['src', 'alt', 'title', 'width', 'height'],
video: ['src', 'controls', 'width', 'height'],
code: ['className'],
// Add custom element 'think'
think: [],
},
tagNames: [
...(defaultSchema.tagNames || []),
'think', // Add custom think tag
],
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/components/MarkdownRender/markdown-without-katex.jsx (1)
208-218: MoverehypeSanitizeto run last in the rehype pipeline.The current order runs
rehypeHighlightafter sanitization. According to rehype-sanitize best practices, anything executed after sanitization must be trusted, since post-sanitize plugins could introduce unsafe nodes that bypass the sanitizer. Unless thesanitizeSchemais explicitly configured to allow all nodes/attributes thatrehypeHighlightemits, sanitization should be the final plugin to ensure all transformations are covered.Proposed change
rehypePlugins={[ rehypeRaw, - [rehypeSanitize, sanitizeSchema], // Add sanitization after rehypeRaw to prevent XSS [ rehypeHighlight, { detect: true, ignoreMissing: true, }, ], + [rehypeSanitize, sanitizeSchema], // Run sanitization last ]}src/components/MarkdownRender/markdown.jsx (1)
211-222: MoverehypeSanitizeto run last in the plugin pipeline.The current order allows
rehypeHighlightto modify the HAST after sanitization runs, bypassing security checks. SincerehypeHighlighttransforms code blocks by adding/modifying span elements with classes, those modifications are not sanitized. Apply the same fix to bothsrc/components/MarkdownRender/markdown.jsx(lines 211–222) andsrc/components/MarkdownRender/markdown-without-katex.jsx(lines 208–218).Proposed change for markdown.jsx
rehypePlugins={[ rehypeKatex, rehypeRaw, - [rehypeSanitize, sanitizeSchema], // Add sanitization after rehypeRaw to prevent XSS [ rehypeHighlight, { detect: true, ignoreMissing: true, }, ], + [rehypeSanitize, sanitizeSchema], // Run sanitization last ]}
🤖 Fix all issues with AI agents
In @src/components/MarkdownRender/markdown-without-katex.jsx:
- Around line 112-138: sanitizeSchema currently lists video attributes but
doesn’t include 'video' in sanitizeSchema.tagNames, so rehypeSanitize will strip
video nodes; add 'video' to the tagNames array alongside 'think', ensure
rehypeSanitize is still applied where intended (references: sanitizeSchema,
tagNames, rehypeSanitize), and while here verify defaultSchema.protocols blocks
javascript: for href/src (confirm your hast-util-sanitize version or explicitly
whitelist safe protocols), and consider tightening the global attributes entry
('*': ['className', 'id', 'dir']) to only include required globals (e.g., just
'className') to reduce attack surface.
🧹 Nitpick comments (1)
src/components/MarkdownRender/markdown.jsx (1)
115-141: Avoid schema drift: extractsanitizeSchemato a shared module used by both renderers.The schema is duplicated in
src/components/MarkdownRender/markdown.jsxandsrc/components/MarkdownRender/markdown-without-katex.jsx; extracting it reduces the chance of future divergence in a security-critical allowlist.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (3)
package.jsonsrc/components/MarkdownRender/markdown-without-katex.jsxsrc/components/MarkdownRender/markdown.jsx
🧰 Additional context used
📓 Path-based instructions (5)
src/**/*.{js,jsx,mjs,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use ESLint and enforce React/JSX standards as configured in
.eslintrc.json
Files:
src/components/MarkdownRender/markdown.jsxsrc/components/MarkdownRender/markdown-without-katex.jsx
src/**/*.{js,jsx,mjs,css,scss}
📄 CodeRabbit inference engine (AGENTS.md)
Use Prettier for consistent code formatting of all JS/JSX/CSS files
Files:
src/components/MarkdownRender/markdown.jsxsrc/components/MarkdownRender/markdown-without-katex.jsx
src/components/**/*.jsx
📄 CodeRabbit inference engine (AGENTS.md)
Reusable UI components should be created in
src/components/directory
Files:
src/components/MarkdownRender/markdown.jsxsrc/components/MarkdownRender/markdown-without-katex.jsx
src/**/*.jsx
📄 CodeRabbit inference engine (AGENTS.md)
Use Preact for React-like components in the extension
Files:
src/components/MarkdownRender/markdown.jsxsrc/components/MarkdownRender/markdown-without-katex.jsx
src/**/*.{js,mjs,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use
webextension-polyfillfor cross-browser API compatibility
Files:
src/components/MarkdownRender/markdown.jsxsrc/components/MarkdownRender/markdown-without-katex.jsx
🧬 Code graph analysis (1)
src/components/MarkdownRender/markdown-without-katex.jsx (1)
src/components/MarkdownRender/markdown.jsx (1)
sanitizeSchema(116-141)
🔇 Additional comments (3)
src/components/MarkdownRender/markdown-without-katex.jsx (1)
3-3:rehype-sanitizeintegration looks correct.src/components/MarkdownRender/markdown.jsx (1)
4-4:rehype-sanitizeimport/use is reasonable for mitigating XSS withrehype-raw.package.json (1)
56-56: Dependency addition verified: lockfile updated and compatibility confirmed.Lockfile regeneration confirmed and
rehype-sanitize@^6.0.0is compatible withreact-markdown@^8.0.7,rehype-raw@^6.1.1, and theunifiedstack. No known advisories for the package itself.
| // Configure sanitize schema to prevent XSS attacks | ||
| const sanitizeSchema = { | ||
| ...defaultSchema, | ||
| attributes: { | ||
| ...defaultSchema.attributes, | ||
| // Allow safe attributes | ||
| '*': ['className', 'id', 'dir'], | ||
| a: ['href', 'title', 'target', 'rel'], | ||
| img: ['src', 'alt', 'title', 'width', 'height'], | ||
| video: ['src', 'controls', 'width', 'height'], | ||
| code: ['className'], | ||
| div: ['className', 'id', 'dir'], | ||
| span: ['className', 'id'], | ||
| // Add custom element 'think' | ||
| think: [], | ||
| }, | ||
| tagNames: [ | ||
| ...(defaultSchema.tagNames || []), | ||
| 'think', // Add custom think tag | ||
| ].filter( | ||
| // Remove dangerous tags | ||
| (tag) => | ||
| !['script', 'iframe', 'object', 'embed', 'link', 'style', 'form', 'input', 'button'].includes( | ||
| tag, | ||
| ), | ||
| ), | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
cat -n src/components/MarkdownRender/markdown-without-katex.jsx | head -150Repository: ChatGPTBox-dev/chatGPTBox
Length of output: 5355
🏁 Script executed:
sed -n '140,200p' src/components/MarkdownRender/markdown-without-katex.jsxRepository: ChatGPTBox-dev/chatGPTBox
Length of output: 1047
🏁 Script executed:
sed -n '200,250p' src/components/MarkdownRender/markdown-without-katex.jsxRepository: ChatGPTBox-dev/chatGPTBox
Length of output: 870
🏁 Script executed:
cat package.json | grep -A 5 -B 5 "rehype-sanitize"Repository: ChatGPTBox-dev/chatGPTBox
Length of output: 399
🌐 Web query:
rehype-sanitize 6.0.0 defaultSchema video tag protocols
💡 Result:
rehype-sanitize@6.0.0 uses the defaultSchema exported by hast-util-sanitize (it does not redefine protocol rules itself). See the rehype-sanitize README and the hast-util-sanitize repo for the canonical defaultSchema (including the protocols map). [1][2]
If you want the exact allowed protocols for video/src (and other URL-bearing properties), inspect the defaultSchema.protocols field in the hast-util-sanitize source. Here are those two references to check:
- rehype-sanitize README (exports defaultSchema). [1]
- hast-util-sanitize (defaultSchema + protocols documented in its README / source). [2]
References:
[1] https://github.com/rehypejs/rehype-sanitize — README (defaultSchema).
[2] https://github.com/syntax-tree/hast-util-sanitize — README and defaultSchema (protocols).
🌐 Web query:
hast-util-sanitize defaultSchema tagNames video protocols javascript data
💡 Result:
You can extend hast-util-sanitize's defaultSchema to allow
const {defaultSchema} = require('hast-util-sanitize') // or: import {defaultSchema} from 'hast-util-sanitize'
const deepmerge = require('deepmerge') // or Object.assign / spread for simple merges
const schema = deepmerge(defaultSchema, {
tagNames: [...(defaultSchema.tagNames || []), 'video', 'source'],
attributes: {
...defaultSchema.attributes,
video: [
// allow common video properties
'controls',
'autoplay',
'loop',
'muted',
'playsInline',
['poster'], // allow poster value
['src'] // allow src on
Notes:
- Do NOT add 'javascript' to protocols (e.g., 'javascript:') — that re-enables JS URLs and is an XSS risk. The defaultSchema blocks javascript: by design. If you need inline blob/mediadata use data: or blob:, not javascript:. [1][2]
- Add any extra allowed attributes or class names similarly (use arrays or regexes where needed). If you use rehype-sanitize, pass this schema to it. [1][3]
Sources:
- hast-util-sanitize docs (defaultSchema, attributes, protocols, tagNames). [1]
- StackOverflow example showing adding 'data' to default src protocols. [2]
- rehype-sanitize README (usage with defaultSchema). [3]
🌐 Web query:
rehype-sanitize allowedElements vs tagNames difference reactmarkdown
💡 Result:
Short answer
- allowedElements is a React‑Markdown prop that filters which HTML tag names React‑Markdown will render (client/renderer-level). [1]
- tagNames is the rehype‑sanitize schema field (passed to rehype-sanitize) that lists which HAST tag names the sanitizer will keep—it’s a lower‑level sanitization schema used when you run rehype‑sanitize (server/AST‑level). [2][3]
Implications / practical differences
- Scope: allowedElements runs inside react-markdown’s rendering pipeline; tagNames is applied by the rehype plugin during AST sanitization. Use allowedElements to quick‑filter rendered tags; use tagNames (and attributes) to configure what rehype‑sanitize will strip or allow. [1][2]
- Attributes/classes: rehype‑sanitize’s schema controls allowed attributes/class names (so enabling a tag there doesn’t automatically allow its className/href/etc.); react-markdown’s allowedElements does not control attributes—if you use rehype-sanitize you must also allow attributes in the sanitizer schema. [2][3]
- Order: when using rehypeRaw + rehypeSanitize, sanitize’s tagNames/attributes must include any tags/props you want preserved (e.g., span + className for syntax highlighting). React‑Markdown’s allowedElements is applied in its own pipeline and won’t override a sanitizer that removed nodes earlier. [2][3]
References
- react-markdown options (allowedElements / disallowedElements). [1]
- rehype-sanitize README / defaultSchema and examples (tagNames, attributes). [2][3]
Sources
[1] react-markdown README (options: allowedElements / disallowedElements).
[2] rehype-sanitize (GitHub README: defaultSchema, tagNames/attributes examples).
[3] rehype-sanitize (npm / examples showing adding tagNames and attributes).
Fix video tag handling: explicitly add to tagNames array in sanitizeSchema.
The video tag and its attributes are defined in sanitizeSchema.attributes and included in allowedElements, but not explicitly added to sanitizeSchema.tagNames. Since rehypeSanitize processes the AST before React-Markdown rendering (line 211), any tag absent from tagNames gets stripped during sanitization. Add 'video' to the tagNames array explicitly (similar to how 'think' is added on line 130) to prevent sanitization from removing video elements.
Additionally:
- Verify
defaultSchema.protocolsblocksjavascript:URLs forhref/srcattributes (should be safe by default, but confirm the hast-util-sanitize version handles this correctly). - Consider reducing global attributes
'*': ['className', 'id', 'dir']to justclassNameifidanddirare not required on all untrusted elements (reduces potential CSS-based or text-direction attack surfaces).
🤖 Prompt for AI Agents
In @src/components/MarkdownRender/markdown-without-katex.jsx around lines 112 -
138, sanitizeSchema currently lists video attributes but doesn’t include 'video'
in sanitizeSchema.tagNames, so rehypeSanitize will strip video nodes; add
'video' to the tagNames array alongside 'think', ensure rehypeSanitize is still
applied where intended (references: sanitizeSchema, tagNames, rehypeSanitize),
and while here verify defaultSchema.protocols blocks javascript: for href/src
(confirm your hast-util-sanitize version or explicitly whitelist safe
protocols), and consider tightening the global attributes entry ('*':
['className', 'id', 'dir']) to only include required globals (e.g., just
'className') to reduce attack surface.
|
@codex review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR addresses a critical XSS vulnerability in the Markdown renderer by implementing HTML sanitization using the rehype-sanitize library. The vulnerability existed because rehype-raw was rendering arbitrary HTML without any validation, potentially allowing attackers to inject malicious scripts through AI responses.
Changes:
- Added
rehype-sanitizedependency (v6.0.0) to sanitize HTML in markdown rendering - Implemented sanitization schema with whitelisted tags and attributes in both markdown renderers
- Configured sanitization to run after
rehypeRawin the rehype plugin pipeline to prevent XSS attacks
Reviewed changes
Copilot reviewed 3 out of 4 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| package.json | Added rehype-sanitize v6.0.0 dependency |
| package-lock.json | Updated lock file with rehype-sanitize and its dependencies |
| src/components/MarkdownRender/markdown.jsx | Implemented HTML sanitization with custom schema to block dangerous tags while preserving safe HTML |
| src/components/MarkdownRender/markdown-without-katex.jsx | Implemented identical sanitization for the non-KaTeX markdown renderer |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Configure sanitize schema to prevent XSS attacks | ||
| const sanitizeSchema = { | ||
| ...defaultSchema, | ||
| attributes: { | ||
| ...defaultSchema.attributes, | ||
| // Allow safe attributes | ||
| '*': ['className', 'id', 'dir'], | ||
| a: ['href', 'title', 'target', 'rel'], | ||
| img: ['src', 'alt', 'title', 'width', 'height'], | ||
| video: ['src', 'controls', 'width', 'height'], | ||
| code: ['className'], | ||
| div: ['className', 'id', 'dir'], | ||
| span: ['className', 'id'], | ||
| // Add custom element 'think' | ||
| think: [], | ||
| }, | ||
| tagNames: [ | ||
| ...(defaultSchema.tagNames || []), | ||
| 'think', // Add custom think tag | ||
| ].filter( | ||
| // Remove dangerous tags | ||
| (tag) => | ||
| !['script', 'iframe', 'object', 'embed', 'link', 'style', 'form', 'input', 'button'].includes( | ||
| tag, | ||
| ), | ||
| ), | ||
| } |
Copilot
AI
Jan 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sanitization schema is missing protocol restrictions, which creates a potential XSS vulnerability. Malicious content can still use dangerous protocols like javascript:, data:, or vbscript: in href and src attributes.
For example, an attacker could inject:
<a href="javascript:alert('XSS')">Click me</a><img src="data:text/html,<script>alert('XSS')</script>">
To fix this, add a protocols configuration to the sanitizeSchema that whitelists only safe protocols. The defaultSchema from rehype-sanitize already includes safe protocol restrictions, but when you spread and override the attributes, you should also explicitly define protocols to ensure they're applied correctly.
Add this to the sanitizeSchema object:
protocols: {
href: ['http', 'https', 'mailto'],
src: ['http', 'https'],
cite: ['http', 'https'],
longdesc: ['http', 'https'],
}
This ensures that only safe HTTP(S) protocols are allowed in URLs, preventing javascript:, data:, and other dangerous URI schemes.
| // Configure sanitize schema to prevent XSS attacks | ||
| const sanitizeSchema = { | ||
| ...defaultSchema, | ||
| attributes: { | ||
| ...defaultSchema.attributes, | ||
| // Allow safe attributes | ||
| '*': ['className', 'id', 'dir'], | ||
| a: ['href', 'title', 'target', 'rel'], | ||
| img: ['src', 'alt', 'title', 'width', 'height'], | ||
| video: ['src', 'controls', 'width', 'height'], | ||
| code: ['className'], | ||
| div: ['className', 'id', 'dir'], | ||
| span: ['className', 'id'], | ||
| // Add custom element 'think' | ||
| think: [], | ||
| }, | ||
| tagNames: [ | ||
| ...(defaultSchema.tagNames || []), | ||
| 'think', // Add custom think tag | ||
| ].filter( | ||
| // Remove dangerous tags | ||
| (tag) => | ||
| !['script', 'iframe', 'object', 'embed', 'link', 'style', 'form', 'input', 'button'].includes( | ||
| tag, | ||
| ), | ||
| ), | ||
| } |
Copilot
AI
Jan 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sanitization schema is missing protocol restrictions, which creates a potential XSS vulnerability. Malicious content can still use dangerous protocols like javascript:, data:, or vbscript: in href and src attributes.
For example, an attacker could inject:
<a href="javascript:alert('XSS')">Click me</a><img src="data:text/html,<script>alert('XSS')</script>">
To fix this, add a protocols configuration to the sanitizeSchema that whitelists only safe protocols. The defaultSchema from rehype-sanitize already includes safe protocol restrictions, but when you spread and override the attributes, you should also explicitly define protocols to ensure they're applied correctly.
Add this to the sanitizeSchema object:
protocols: {
href: ['http', 'https', 'mailto'],
src: ['http', 'https'],
cite: ['http', 'https'],
longdesc: ['http', 'https'],
}
This ensures that only safe HTTP(S) protocols are allowed in URLs, preventing javascript:, data:, and other dangerous URI schemes.
| ...defaultSchema.attributes, | ||
| // Allow safe attributes | ||
| '*': ['className', 'id', 'dir'], | ||
| a: ['href', 'title', 'target', 'rel'], |
Copilot
AI
Jan 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sanitization schema allows the 'target' attribute on anchor tags without restricting its values. While not directly an XSS vulnerability, this can be exploited for tabnabbing attacks where malicious links with target="_blank" can access the window.opener object.
To mitigate this, you should either:
- Remove 'target' from the allowed attributes for anchor tags, or
- Ensure that links with
target="_blank"automatically getrel="noopener noreferrer"added
Consider updating the anchor tag configuration to enforce safe rel values or remove the target attribute allowance.
| a: ['href', 'title', 'target', 'rel'], | |
| a: ['href', 'title', 'rel'], |
| ...defaultSchema.attributes, | ||
| // Allow safe attributes | ||
| '*': ['className', 'id', 'dir'], | ||
| a: ['href', 'title', 'target', 'rel'], |
Copilot
AI
Jan 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sanitization schema allows the 'target' attribute on anchor tags without restricting its values. While not directly an XSS vulnerability, this can be exploited for tabnabbing attacks where malicious links with target="_blank" can access the window.opener object.
To mitigate this, you should either:
- Remove 'target' from the allowed attributes for anchor tags, or
- Ensure that links with
target="_blank"automatically getrel="noopener noreferrer"added
Consider updating the anchor tag configuration to enforce safe rel values or remove the target attribute allowance.
| a: ['href', 'title', 'target', 'rel'], | |
| a: ['href', 'title', 'rel'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 52142e891d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| attributes: { | ||
| ...defaultSchema.attributes, | ||
| // Allow safe attributes | ||
| '*': ['className', 'id', 'dir'], | ||
| a: ['href', 'title', 'target', 'rel'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Allow KaTeX inline styles in sanitize schema
The sanitize schema overrides attributes['*'] to only className, id, and dir, so any inline style attributes are stripped. Because rehypeSanitize runs after rehypeKatex, this removes the inline styles KaTeX emits for layout/positioning (e.g., strut heights/vertical alignment), which can cause math formulas to render incorrectly or collapse. Consider whitelisting style for the KaTeX-related tags (e.g., span, div) or extending the schema with KaTeX’s known safe attributes so math rendering remains intact.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @src/components/MarkdownRender/markdown.jsx:
- Around line 126-127: The allowed-attributes mapping in MarkdownRender (the
entries with div: ['className', 'id', 'dir', 'style'] and span: ['className',
'id', 'style']) is permitting inline styles which can enable CSS-based attacks;
remove 'style' from both arrays so they become div: ['className', 'id', 'dir']
and span: ['className', 'id'] and mirror the behavior in
markdown-without-katex.jsx; search for other occurrences of these attribute
lists in MarkdownRender or related sanitizer config and remove 'style' there as
well to ensure consistent sanitization.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/components/MarkdownRender/markdown.jsx
🧰 Additional context used
📓 Path-based instructions (5)
src/**/*.{js,jsx,mjs,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use ESLint and enforce React/JSX standards as configured in
.eslintrc.json
Files:
src/components/MarkdownRender/markdown.jsx
src/**/*.{js,jsx,mjs,css,scss}
📄 CodeRabbit inference engine (AGENTS.md)
Use Prettier for consistent code formatting of all JS/JSX/CSS files
Files:
src/components/MarkdownRender/markdown.jsx
src/components/**/*.jsx
📄 CodeRabbit inference engine (AGENTS.md)
Reusable UI components should be created in
src/components/directory
Files:
src/components/MarkdownRender/markdown.jsx
src/**/*.jsx
📄 CodeRabbit inference engine (AGENTS.md)
Use Preact for React-like components in the extension
Files:
src/components/MarkdownRender/markdown.jsx
src/**/*.{js,mjs,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use
webextension-polyfillfor cross-browser API compatibility
Files:
src/components/MarkdownRender/markdown.jsx
🧬 Code graph analysis (1)
src/components/MarkdownRender/markdown.jsx (1)
src/components/MarkdownRender/markdown-without-katex.jsx (1)
sanitizeSchema(113-138)
🔇 Additional comments (4)
src/components/MarkdownRender/markdown.jsx (4)
4-4: LGTM!The import correctly brings in both the plugin and
defaultSchemafor schema extension, which is the recommended approach for rehype-sanitize v6.
131-140: Tag filtering logic is correct.The approach of extending
defaultSchema.tagNames, adding the customthinktag, and filtering out dangerous tags (script,iframe,object,embed,link,style,form,input,button) is a sound defense-in-depth strategy.
211-222: Plugin ordering is correct for XSS prevention.Placing
rehypeSanitizeafterrehypeRawensures that any raw HTML parsed byrehypeRawis properly sanitized before further processing. This is the correct sequence for preventing XSS.
122-124: No action required—protocol sanitization is correctly preserved.The spread operator (
...defaultSchema) at line 117 preserves all defaultSchema properties, including the separateprotocolsconfiguration. Overriding only theattributesproperty does not affect protocol filtering; dangerous URL schemes (javascript:, data:, file:, etc.) remain blocked forhrefandsrcattributes as configured in defaultSchema.
| div: ['className', 'id', 'dir', 'style'], | ||
| span: ['className', 'id', 'style'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove style attribute to prevent CSS-based attacks.
Allowing the style attribute on div and span reintroduces security risks this PR aims to fix:
- CSS injection can enable UI redressing and clickjacking
- Legacy browsers may execute JavaScript via CSS expressions (e.g.,
expression()) - CSS can exfiltrate data via
url()properties
This is also inconsistent with markdown-without-katex.jsx, which correctly omits style from these elements.
Proposed fix
- div: ['className', 'id', 'dir', 'style'],
- span: ['className', 'id', 'style'],
+ div: ['className', 'id', 'dir'],
+ span: ['className', 'id'],📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| div: ['className', 'id', 'dir', 'style'], | |
| span: ['className', 'id', 'style'], | |
| div: ['className', 'id', 'dir'], | |
| span: ['className', 'id'], |
🤖 Prompt for AI Agents
In @src/components/MarkdownRender/markdown.jsx around lines 126 - 127, The
allowed-attributes mapping in MarkdownRender (the entries with div:
['className', 'id', 'dir', 'style'] and span: ['className', 'id', 'style']) is
permitting inline styles which can enable CSS-based attacks; remove 'style' from
both arrays so they become div: ['className', 'id', 'dir'] and span:
['className', 'id'] and mirror the behavior in markdown-without-katex.jsx;
search for other occurrences of these attribute lists in MarkdownRender or
related sanitizer config and remove 'style' there as well to ensure consistent
sanitization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 3 out of 4 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| div: ['className', 'id', 'dir', 'style'], | ||
| span: ['className', 'id', 'style'], |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Allowing the 'style' attribute on div and span elements can introduce XSS vulnerabilities through CSS injection. Attackers can use properties like 'expression()' in IE or 'url()' with javascript: protocol, or abuse CSS to exfiltrate data. Consider removing 'style' from the allowed attributes or using a more restrictive CSS sanitizer if inline styles are necessary.
| div: ['className', 'id', 'dir', 'style'], | |
| span: ['className', 'id', 'style'], | |
| div: ['className', 'id', 'dir'], | |
| span: ['className', 'id'], |
| ...defaultSchema.attributes, | ||
| // Allow safe attributes | ||
| '*': ['className', 'id', 'dir'], | ||
| a: ['href', 'title', 'target', 'rel'], |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By explicitly defining the 'href' attribute for anchor tags, this configuration overrides the default schema's protocol validation. The defaultSchema includes protocol filtering to prevent javascript: and other dangerous protocols. Consider explicitly adding protocol restrictions using the 'protocols' configuration option or ensuring dangerous protocols are filtered.
| img: ['src', 'alt', 'title', 'width', 'height'], | ||
| video: ['src', 'controls', 'width', 'height'], |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By explicitly defining the 'src' attribute for img and video tags, this configuration overrides the default schema's protocol validation. The defaultSchema includes protocol filtering to prevent javascript: and other dangerous protocols. Consider explicitly adding protocol restrictions using the 'protocols' configuration option or ensuring dangerous protocols are filtered.
| img: ['src', 'alt', 'title', 'width', 'height'], | ||
| video: ['src', 'controls', 'width', 'height'], |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By explicitly defining the 'src' attribute for img and video tags, this configuration overrides the default schema's protocol validation. The defaultSchema includes protocol filtering to prevent javascript: and other dangerous protocols. Consider explicitly adding protocol restrictions using the 'protocols' configuration option or ensuring dangerous protocols are filtered.
| // Configure sanitize schema to prevent XSS attacks | ||
| const sanitizeSchema = { | ||
| ...defaultSchema, | ||
| attributes: { | ||
| ...defaultSchema.attributes, | ||
| // Allow safe attributes | ||
| '*': ['className', 'id', 'dir'], | ||
| a: ['href', 'title', 'target', 'rel'], | ||
| img: ['src', 'alt', 'title', 'width', 'height'], | ||
| video: ['src', 'controls', 'width', 'height'], | ||
| code: ['className'], | ||
| div: ['className', 'id', 'dir', 'style'], | ||
| span: ['className', 'id', 'style'], | ||
| // Add custom element 'think' | ||
| think: [], | ||
| }, | ||
| tagNames: [ | ||
| ...(defaultSchema.tagNames || []), | ||
| 'think', // Add custom think tag | ||
| ].filter( | ||
| // Remove dangerous tags | ||
| (tag) => | ||
| !['script', 'iframe', 'object', 'embed', 'link', 'style', 'form', 'input', 'button'].includes( | ||
| tag, | ||
| ), | ||
| ), | ||
| } |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sanitizeSchema configuration is duplicated between markdown.jsx and markdown-without-katex.jsx with a minor difference (div and span 'style' attribute). Consider extracting this to a shared module to reduce duplication and ensure consistency across both renderers.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 3 out of 4 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@codex review |
|
Codex Review: Didn't find any major issues. Keep it up! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
User description
🔒 Security: Fix Critical XSS Vulnerability in Markdown Renderer
Vulnerability ID: VULN-001
Type: Cross-Site Scripting (XSS)
Severity: Critical
CVSS Score: 9.6 (AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H)
🎯 Problem Statement
The Markdown renderer was using
rehype-rawplugin to render arbitrary HTML without any sanitization, creating a critical XSS vulnerability.Vulnerable Code (Before):
Attack Scenario:
Malicious AI Response Injection: Attacker controls or intercepts AI API responses (via MITM, DNS hijacking, or malicious custom API endpoint)
Inject Malicious HTML:
Exploit Extension Privileges: Since the extension has:
host_permissions: ["<all_urls>"]- Access to all websitescookiespermission - Read all cookiesstoragepermission - Access to API keys and tokensImpact: Complete compromise of:
✅ Solution
Added
rehype-sanitizewith strict schema configuration to whitelist only safe HTML elements and attributes.Fixed Code (After):
Security Features:
✅ Whitelist approach: Only explicitly allowed tags/attributes pass through
✅ Block dangerous elements:
<script>,<iframe>,<object>,<embed>,<form>, etc.✅ Strip event handlers:
onclick,onerror,onload, etc. are removed✅ Preserve functionality: Safe HTML like tables, code blocks, links still work
✅ Custom elements: Properly handle custom
<think>tag📝 Changes Made
Files Modified:
✅
src/components/MarkdownRender/markdown.jsxrehypeSanitizeimportsanitizeSchemaconfiguration✅
src/components/MarkdownRender/markdown-without-katex.jsx✅
package.json&package-lock.jsonrehype-sanitizedependencyLines of Code:
🧪 Testing
XSS Test Cases (All Blocked ✅):
Expected Result: All malicious code is stripped. Only safe text/elements remain.
Functional Testing:
✅ Safe Markdown still works:
<think>component🚀 Deployment Impact
User Impact:
Compatibility:
📊 Security Audit Context
This fix addresses VULN-001 from the comprehensive security audit report (
SECURITY_AUDIT_REPORT.md).Related Issues:
Full Audit: See
SECURITY_AUDIT_REPORT.mdfor complete findings.📋 Checklist
🔐 Responsible Disclosure
This vulnerability was discovered through an internal security audit. No evidence of active exploitation has been found.
Timeline:
Severity Justification:
🎓 References
👥 Credits
📞 Questions?
For security-related questions or to report vulnerabilities privately, please contact:
DO NOT disclose security details publicly until a fix is released.
🏁 Merge Recommendation
Priority: 🔴 URGENT - Immediate Merge Recommended
This is a critical security fix that protects all users from potential API key theft. No functional changes, only security hardening. Safe to merge and release immediately.
Suggested Release: v2.5.10 (security patch)
PR Type
Bug fix
Description
Added
rehype-sanitizedependency to prevent XSS attacksImplemented strict HTML sanitization schema in both Markdown renderers
Whitelist safe HTML tags and attributes, block dangerous elements
Sanitization applied after
rehypeRawto prevent script injectionDiagram Walkthrough
File Walkthrough
package.json
Add rehype-sanitize dependencypackage.json
rehype-sanitizeversion^6.0.0as a new dependencymarkdown.jsx
Implement HTML sanitization in Markdown renderersrc/components/MarkdownRender/markdown.jsx
rehypeSanitizeanddefaultSchemafrom rehype-sanitizesanitizeSchemaconfiguration object with whitelist of safeHTML tags and attributes
script,iframe,object,embed,link,style,form,input,buttonrehypeRawtoprevent XSS attacks
markdown-without-katex.jsx
Implement HTML sanitization in non-KaTeX Markdown renderersrc/components/MarkdownRender/markdown-without-katex.jsx
rehypeSanitizeanddefaultSchemafrom rehype-sanitizesanitizeSchemaconfiguration with safe tag andattribute whitelist
script,iframe,object,embed,link,style,form,input,buttonrehypeRawtoprevent XSS attacks
Summary by CodeRabbit
Bug Fixes
Chores
✏️ Tip: You can customize this high-level summary in your review settings.