Skip to content

Conversation

@orhanrauf
Copy link
Contributor

@orhanrauf orhanrauf commented Oct 28, 2025

Overview

Fixes ENG-181 - Replaced all 12 instances of unsafe .innerHTML assignments with secure DOM manipulation to prevent XSS attacks.

Security Impact

Eliminates XSS vulnerability: Even if an attacker injects malicious HTML into short_name fields, it will be rendered as plain text rather than executed as code
Defense-in-depth: Provides additional security layer beyond existing CSP headers
Zero innerHTML usage: Confirmed via grep - no unsafe innerHTML assignments remain

Changes Summary

Before (Vulnerable)

e.currentTarget.parentElement!.innerHTML = \`<span>\${shortName.substring(0, 2).toUpperCase()}</span>\`;

After (Safe)

const span = document.createElement('span');
span.className = '...';
span.textContent = shortName.substring(0, 2).toUpperCase(); // Safe - no HTML interpretation
parent.appendChild(span);

Files Modified (12 instances)

Button Components:

  • frontend/src/components/dashboard/SourceButton.tsx
  • frontend/src/components/dashboard/AuthProviderButton.tsx
  • frontend/src/components/dashboard/SmallSourceButton.tsx

View Components:

  • frontend/src/components/shared/views/EditAuthProviderView.tsx
  • frontend/src/components/shared/views/CreateCollectionView.tsx
  • frontend/src/components/shared/views/ConnectionErrorView.tsx
  • frontend/src/components/auth-providers/AuthProviderDetailView.tsx (2 instances)

Other Components:

  • frontend/src/pages/SemanticMcp.tsx (2 instances)
  • frontend/src/components/collection/EditSourceConnectionDialog.tsx
  • frontend/src/components/sync/nodes/TransformerNode.tsx

Testing Checklist

  • Force icon failures (block icon CDNs in DevTools)
  • Verify fallback initials display correctly
  • Check no console errors appear
  • Test edge cases (empty/single char/special char shortNames)

Compliance

Addresses CASA TAC Security requirement 41: proper output encoding for all contexts.


Summary by cubic

Fixes XSS risk by replacing all unsafe innerHTML fallbacks with safe DOM manipulation for icon initials. Addresses ENG-181 and meets CASA TAC Security requirement 41.

  • Bug Fixes
    • Replaced innerHTML with createElement + textContent in 12 places across buttons, views, dialogs, and nodes.
    • Fallback initials now render as plain text; theme classes applied via classList.
    • Verified no remaining innerHTML usage.

Replace all 12 instances of unsafe innerHTML assignments with secure DOM
manipulation using createElement() and textContent to prevent XSS attacks.

The vulnerable pattern used template literals with user-controlled data
(shortName fields) without sanitization. Even though these fields currently
come from internal sources, this provides defense-in-depth protection.

Changes:
- Replace innerHTML with createElement() + textContent in icon fallbacks
- Affected components: buttons, views, dialogs, and transformer nodes
- All fallback initials now safely rendered as plain text

Resolves: ENG-181
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 10 files

Prompt for AI agents (all 4 issues)

Understand the root cause of the following 4 issues and fix them.


<file name="frontend/src/components/collection/EditSourceConnectionDialog.tsx">

<violation number="1" location="frontend/src/components/collection/EditSourceConnectionDialog.tsx:115">
Use replaceChildren (or clear the parent) before appending the fallback wrapper so multiple error events don&#39;t stack duplicate fallback nodes.</violation>
</file>

<file name="frontend/src/components/sync/nodes/TransformerNode.tsx">

<violation number="1" location="frontend/src/components/sync/nodes/TransformerNode.tsx:43">
Each onError run appends another fallback span, so repeated icon load failures (e.g., after theme toggles) stack multiple initials badges instead of keeping just one. Please clear or reuse the existing fallback node before appending a new one.</violation>
</file>

<file name="frontend/src/components/auth-providers/AuthProviderDetailView.tsx">

<violation number="1" location="frontend/src/components/auth-providers/AuthProviderDetailView.tsx:575">
This fallback also uses appendChild without clearing the parent, so repeated onError executions stack multiple fallback wrappers and distort the button layout. Please replace the existing child nodes before inserting the fallback.</violation>
</file>

<file name="frontend/src/components/shared/views/EditAuthProviderView.tsx">

<violation number="1" location="frontend/src/components/shared/views/EditAuthProviderView.tsx:269">
Appending the fallback wrapper without clearing the parent means each image error adds another fallback element, diverging from the prior behavior and producing duplicate initials when the handler fires again (e.g., on theme/icon changes). Replace the parent’s children instead of appending so only one fallback remains.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.

span.textContent = sourceConnection.short_name.substring(0, 2).toUpperCase();

wrapper.appendChild(span);
parent.appendChild(wrapper);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 28, 2025

Choose a reason for hiding this comment

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

Use replaceChildren (or clear the parent) before appending the fallback wrapper so multiple error events don't stack duplicate fallback nodes.

Prompt for AI agents
Address the following comment on frontend/src/components/collection/EditSourceConnectionDialog.tsx at line 115:

<comment>Use replaceChildren (or clear the parent) before appending the fallback wrapper so multiple error events don&#39;t stack duplicate fallback nodes.</comment>

<file context>
@@ -101,13 +101,18 @@ export const EditSourceConnectionDialog: React.FC&lt;EditSourceConnectionDialogProp
+                                            span.textContent = sourceConnection.short_name.substring(0, 2).toUpperCase();
+
+                                            wrapper.appendChild(span);
+                                            parent.appendChild(wrapper);
                                         }}
                                     /&gt;
</file context>
Fix with Cubic

const span = document.createElement('span');
span.className = 'text-foreground font-medium text-xs';
span.textContent = initials;
parent.appendChild(span);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 28, 2025

Choose a reason for hiding this comment

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

Each onError run appends another fallback span, so repeated icon load failures (e.g., after theme toggles) stack multiple initials badges instead of keeping just one. Please clear or reuse the existing fallback node before appending a new one.

Prompt for AI agents
Address the following comment on frontend/src/components/sync/nodes/TransformerNode.tsx at line 43:

<comment>Each onError run appends another fallback span, so repeated icon load failures (e.g., after theme toggles) stack multiple initials badges instead of keeping just one. Please clear or reuse the existing fallback node before appending a new one.</comment>

<file context>
@@ -33,8 +33,14 @@ export const TransformerNode = memo(({ data, selected, ...props }: NodeProps) =&gt;
+              const span = document.createElement(&#39;span&#39;);
+              span.className = &#39;text-foreground font-medium text-xs&#39;;
+              span.textContent = initials;
+              parent.appendChild(span);
             }}
           /&gt;
</file context>
Fix with Cubic

span.textContent = 'AW';

wrapper.appendChild(span);
parent.appendChild(wrapper);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 28, 2025

Choose a reason for hiding this comment

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

This fallback also uses appendChild without clearing the parent, so repeated onError executions stack multiple fallback wrappers and distort the button layout. Please replace the existing child nodes before inserting the fallback.

Prompt for AI agents
Address the following comment on frontend/src/components/auth-providers/AuthProviderDetailView.tsx at line 575:

<comment>This fallback also uses appendChild without clearing the parent, so repeated onError executions stack multiple fallback wrappers and distort the button layout. Please replace the existing child nodes before inserting the fallback.</comment>

<file context>
@@ -561,13 +561,18 @@ export const AuthProviderDetailView: React.FC&lt;AuthProviderDetailViewProps&gt; = ({
+                                                    span.textContent = &#39;AW&#39;;
+
+                                                    wrapper.appendChild(span);
+                                                    parent.appendChild(wrapper);
                                                 }}
                                             /&gt;
</file context>
Suggested change
parent.appendChild(wrapper);
parent.replaceChildren(wrapper);
Fix with Cubic

span.textContent = authProviderShortName.substring(0, 2).toUpperCase();

wrapper.appendChild(span);
parent.appendChild(wrapper);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 28, 2025

Choose a reason for hiding this comment

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

Appending the fallback wrapper without clearing the parent means each image error adds another fallback element, diverging from the prior behavior and producing duplicate initials when the handler fires again (e.g., on theme/icon changes). Replace the parent’s children instead of appending so only one fallback remains.

Prompt for AI agents
Address the following comment on frontend/src/components/shared/views/EditAuthProviderView.tsx at line 269:

<comment>Appending the fallback wrapper without clearing the parent means each image error adds another fallback element, diverging from the prior behavior and producing duplicate initials when the handler fires again (e.g., on theme/icon changes). Replace the parent’s children instead of appending so only one fallback remains.</comment>

<file context>
@@ -253,16 +253,21 @@ export const EditAuthProviderView: React.FC&lt;EditAuthProviderViewProps&gt; = ({
+                                    span.textContent = authProviderShortName.substring(0, 2).toUpperCase();
+
+                                    wrapper.appendChild(span);
+                                    parent.appendChild(wrapper);
+                                }}
                                 /&gt;
</file context>
Suggested change
parent.appendChild(wrapper);
parent.replaceChildren(wrapper);
Fix with Cubic

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@cubic-dev-ai i'm not sure if i understand, can you explain a bit more?

Copy link
Contributor

Choose a reason for hiding this comment

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

@orhanrauf Of course. My concern was about what happens if the onError handler runs more than once.

The previous code used innerHTML = '...', which completely replaced the parent's content with the fallback initials. Your new code uses parent.appendChild(wrapper), which adds the fallback initials without removing what was already there.

If an event (like a theme change) causes the component to re-render and the image fails to load again, onError will fire a second time. Your current implementation would append a second set of initials, leading to duplicates.

I suggested using parent.replaceChildren(wrapper) because it clears any existing elements from the parent before adding the new wrapper. This mimics the original behavior and ensures only one set of initials is ever displayed.

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