Skip to content

Security: Untrusted stylesheet URL is injected into document head#1234

Open
tuanaiseo wants to merge 1 commit intosuitenumerique:mainfrom
tuanaiseo:contribai/fix/security/untrusted-stylesheet-url-is-injected-int
Open

Security: Untrusted stylesheet URL is injected into document head#1234
tuanaiseo wants to merge 1 commit intosuitenumerique:mainfrom
tuanaiseo:contribai/fix/security/untrusted-stylesheet-url-is-injected-int

Conversation

@tuanaiseo
Copy link
Copy Markdown

Problem

The frontend loads custom_css_url from runtime config and directly appends it as a <link rel="stylesheet"> without origin allowlisting or scheme validation. If an attacker can influence this config, they can inject hostile CSS to perform UI redressing, data exfiltration via CSS side channels, or phishing overlays.

Severity: high
File: src/frontend/src/components/AppInitialization.tsx

Solution

Validate custom_css_url against a strict allowlist of trusted origins and https: scheme before injecting. Reject or ignore untrusted URLs, and consider serving tenant customization styles from trusted backend storage instead of arbitrary external URLs.

Changes

  • src/frontend/src/components/AppInitialization.tsx (modified)

Testing

  • Existing tests pass
  • Manual review completed
  • No new warnings/errors introduced

…cument

The frontend loads `custom_css_url` from runtime config and directly appends it as a `<link rel="stylesheet">` without origin allowlisting or scheme validation. If an attacker can influence this config, they can inject hostile CSS to perform UI redressing, data exfiltration via CSS side channels, or phishing overlays.

Affected files: AppInitialization.tsx

Signed-off-by: tuanaiseo <221258316+tuanaiseo@users.noreply.github.com>
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 3, 2026

@lebaudantoine
Copy link
Copy Markdown
Collaborator

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

✅ Actions performed

Full review triggered.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

Walkthrough

The stylesheet injection logic in AppInitialization.tsx now validates custom CSS URLs before appending them to the document head. The code parses the provided custom_css_url and verifies it uses the https: protocol and matches the current origin. The link element is only appended when both conditions are met; parsing failures are silently ignored and result in no link injection.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main security issue: untrusted stylesheet URLs being injected into the document head without proper validation.
Description check ✅ Passed The description comprehensively explains the security problem, the solution approach, and the affected file, directly relating to the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/frontend/src/components/AppInitialization.tsx`:
- Around line 22-41: The useEffect in the AppInitialization component that
injects the <link> for custom_css_url must add a cleanup to remove the injected
element (id 'meet-custom-css') when custom_css_url changes or the component
unmounts; update the effect (the function that creates the link element) to
first remove any existing element with id 'meet-custom-css' (or return a cleanup
function that removes it) so you don't leave duplicate or stale stylesheets in
the DOM, and keep the try/catch around URL parsing while ensuring the cleanup
runs even if parsing fails.
- Around line 38-39: The empty catch block in AppInitialization.tsx silently
swallows errors when loading or applying the configured custom_css_url; update
the catch to log a warning (including the caught error) when not in production
so configuration problems are visible—e.g., in the try/catch around
custom_css_url handling inside the AppInitialization component, change the catch
to accept an error parameter and call console.warn or a logger like
process.env.NODE_ENV !== 'production' && console.warn("Failed to load
custom_css_url:", error) (or use your app logger) so the message includes
context and the error object.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d07edac1-cf44-4377-b6f1-c55933748e0d

📥 Commits

Reviewing files that changed from the base of the PR and between 6b656ee and 0851b40.

📒 Files selected for processing (1)
  • src/frontend/src/components/AppInitialization.tsx

Comment on lines 22 to 41
useEffect(() => {
if (custom_css_url) {
const link = document.createElement('link')
link.href = custom_css_url
link.id = 'meet-custom-css'
link.rel = 'stylesheet'
document.head.appendChild(link)
try {
const customCssUrl = new URL(custom_css_url, window.location.origin)
const trustedOrigins = [window.location.origin]

if (
customCssUrl.protocol === 'https:' &&
trustedOrigins.includes(customCssUrl.origin)
) {
const link = document.createElement('link')
link.href = customCssUrl.toString()
link.id = 'meet-custom-css'
link.rel = 'stylesheet'
document.head.appendChild(link)
}
} catch {
}
}
}, [custom_css_url])
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing cleanup function in useEffect.

If custom_css_url changes or the component unmounts, the previously injected <link> element remains in the DOM. This could cause duplicate stylesheets or stale styles persisting after configuration changes.

🔧 Proposed fix to add cleanup
   useEffect(() => {
+    let link: HTMLLinkElement | null = null
     if (custom_css_url) {
       try {
         const customCssUrl = new URL(custom_css_url, window.location.origin)
         const trustedOrigins = [window.location.origin]

         if (
           customCssUrl.protocol === 'https:' &&
           trustedOrigins.includes(customCssUrl.origin)
         ) {
-          const link = document.createElement('link')
+          link = document.createElement('link')
           link.href = customCssUrl.toString()
           link.id = 'meet-custom-css'
           link.rel = 'stylesheet'
           document.head.appendChild(link)
         }
       } catch {
       }
     }
+    return () => {
+      if (link) {
+        link.remove()
+      }
+    }
   }, [custom_css_url])
📝 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.

Suggested change
useEffect(() => {
if (custom_css_url) {
const link = document.createElement('link')
link.href = custom_css_url
link.id = 'meet-custom-css'
link.rel = 'stylesheet'
document.head.appendChild(link)
try {
const customCssUrl = new URL(custom_css_url, window.location.origin)
const trustedOrigins = [window.location.origin]
if (
customCssUrl.protocol === 'https:' &&
trustedOrigins.includes(customCssUrl.origin)
) {
const link = document.createElement('link')
link.href = customCssUrl.toString()
link.id = 'meet-custom-css'
link.rel = 'stylesheet'
document.head.appendChild(link)
}
} catch {
}
}
}, [custom_css_url])
useEffect(() => {
let link: HTMLLinkElement | null = null
if (custom_css_url) {
try {
const customCssUrl = new URL(custom_css_url, window.location.origin)
const trustedOrigins = [window.location.origin]
if (
customCssUrl.protocol === 'https:' &&
trustedOrigins.includes(customCssUrl.origin)
) {
link = document.createElement('link')
link.href = customCssUrl.toString()
link.id = 'meet-custom-css'
link.rel = 'stylesheet'
document.head.appendChild(link)
}
} catch {
}
}
return () => {
if (link) {
link.remove()
}
}
}, [custom_css_url])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/frontend/src/components/AppInitialization.tsx` around lines 22 - 41, The
useEffect in the AppInitialization component that injects the <link> for
custom_css_url must add a cleanup to remove the injected element (id
'meet-custom-css') when custom_css_url changes or the component unmounts; update
the effect (the function that creates the link element) to first remove any
existing element with id 'meet-custom-css' (or return a cleanup function that
removes it) so you don't leave duplicate or stale stylesheets in the DOM, and
keep the try/catch around URL parsing while ensuring the cleanup runs even if
parsing fails.

Comment on lines +38 to +39
} catch {
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Silent error swallowing hinders debugging.

An empty catch block makes it difficult to diagnose configuration issues when an invalid custom_css_url is provided. Consider logging a warning in non-production environments.

🛠️ Suggested improvement
-      } catch {
+      } catch (error) {
+        if (process.env.NODE_ENV !== 'production') {
+          console.warn('Invalid custom_css_url:', custom_css_url, error)
+        }
       }
📝 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.

Suggested change
} catch {
}
} catch (error) {
if (process.env.NODE_ENV !== 'production') {
console.warn('Invalid custom_css_url:', custom_css_url, error)
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/frontend/src/components/AppInitialization.tsx` around lines 38 - 39, The
empty catch block in AppInitialization.tsx silently swallows errors when loading
or applying the configured custom_css_url; update the catch to log a warning
(including the caught error) when not in production so configuration problems
are visible—e.g., in the try/catch around custom_css_url handling inside the
AppInitialization component, change the catch to accept an error parameter and
call console.warn or a logger like process.env.NODE_ENV !== 'production' &&
console.warn("Failed to load custom_css_url:", error) (or use your app logger)
so the message includes context and the error object.

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