-
Notifications
You must be signed in to change notification settings - Fork 22
chore: load katex with async stylesheet #4680
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
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
| {/* Fallback for users with JavaScript disabled */} | ||
| <noscript | ||
| dangerouslySetInnerHTML={{ | ||
| __html: `<link rel="stylesheet" href="${href}" crossorigin="${crossOrigin}" />` |
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 href and crossOrigin props are directly interpolated into HTML without escaping, creating an XSS vulnerability through dangerouslySetInnerHTML.
View Details
📝 Patch Details
diff --git a/packages/fern-docs/bundle/src/components/AsyncStylesheet.tsx b/packages/fern-docs/bundle/src/components/AsyncStylesheet.tsx
index dd91da19d..785fd357e 100644
--- a/packages/fern-docs/bundle/src/components/AsyncStylesheet.tsx
+++ b/packages/fern-docs/bundle/src/components/AsyncStylesheet.tsx
@@ -8,32 +8,69 @@
* maintaining React/TypeScript compatibility.
*/
+/**
+ * Validates and sanitizes a URL to prevent XSS attacks
+ * Returns null if the URL is invalid or potentially malicious
+ */
+function validateStylesheetUrl(href: string): string | null {
+ try {
+ // Only allow http/https URLs
+ const url = new URL(href, typeof window !== 'undefined' ? window.location.href : 'http://localhost');
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
+ return null;
+ }
+ return url.toString();
+ } catch {
+ return null;
+ }
+}
+
+/**
+ * Validates crossOrigin value to ensure it's one of the allowed values
+ */
+function validateCrossOrigin(crossOrigin: string): "anonymous" | "use-credentials" | null {
+ if (crossOrigin === "anonymous" || crossOrigin === "use-credentials") {
+ return crossOrigin;
+ }
+ return null;
+}
+
interface AsyncStylesheetProps {
href: string;
crossOrigin?: "anonymous" | "use-credentials";
}
export function AsyncStylesheet({ href, crossOrigin = "anonymous" }: AsyncStylesheetProps) {
+ // Validate and sanitize inputs to prevent XSS
+ const validatedHref = validateStylesheetUrl(href);
+ const validatedCrossOrigin = validateCrossOrigin(crossOrigin);
+
+ // If validation fails, don't render anything to prevent XSS
+ if (!validatedHref || !validatedCrossOrigin) {
+ console.warn('AsyncStylesheet: Invalid href or crossOrigin prop detected, skipping render for security');
+ return null;
+ }
+
return (
<>
{/* Preload hint for early resource discovery */}
- <link rel="preload" as="style" href={href} crossOrigin={crossOrigin} />
+ <link rel="preload" as="style" href={validatedHref} crossOrigin={validatedCrossOrigin} />
{/* Async loading with media swap trick - React's onLoad works here */}
<link
rel="stylesheet"
- href={href}
+ href={validatedHref}
media="print"
- crossOrigin={crossOrigin}
+ crossOrigin={validatedCrossOrigin}
onLoad={(e) => {
(e.target as HTMLLinkElement).media = "all";
}}
/>
- {/* Fallback for users with JavaScript disabled */}
+ {/* Fallback for users with JavaScript disabled - now safe because we validated inputs */}
<noscript
dangerouslySetInnerHTML={{
- __html: `<link rel="stylesheet" href="${href}" crossorigin="${crossOrigin}" />`
+ __html: `<link rel="stylesheet" href="${validatedHref}" crossorigin="${validatedCrossOrigin}" />`
}}
/>
</>
Analysis
XSS vulnerability in AsyncStylesheet component via dangerouslySetInnerHTML
What fails: AsyncStylesheet component in packages/fern-docs/bundle/src/components/AsyncStylesheet.tsx directly interpolates untrusted href and crossOrigin props into HTML via dangerouslySetInnerHTML, allowing script execution
How to reproduce:
<AsyncStylesheet
href='x" onload="alert(1)'
crossOrigin="anonymous"
/>Result: Generated HTML contains executable JavaScript: <link rel="stylesheet" href="x" onload="alert(1)" crossorigin="anonymous" /> which executes when parsed
Expected: Input should be validated/sanitized to prevent XSS attacks per React security guidelines
No description provided.