Skip to content

bug: fix stored XSS in About page feedback slideshow - #331

Open
devin-ai-integration[bot] wants to merge 1 commit into
developfrom
devin/1789000380-about-feedback-xss
Open

devin-ai-integration[bot] wants to merge 1 commit into
developfrom
devin/1789000380-about-feedback-xss

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Sep 10, 2026

Copy link
Copy Markdown

Description

Fixes a stored XSS on the About page (frontend/src/app/about/about.component.ts). populateSlideshowFromFeedbacks() concatenated each user-submitted feedback comment into an HTML string, wrapped it in DomSanitizer.bypassSecurityTrustHtml() and the template bound it via [innerHTML]="item?.args", so any payload that survived the backend's single-pass sanitizer executed for every /about visitor.

Fix (frontend only, breaks the sink):

  • about.component.ts: drop DomSanitizer and the stars HTML strings; gallery args is now { comment: string, stars: string[] } built by starIcons(rating).
  • about.component.html: <figure> builds the caption structurally — comment rendered with {{ }} interpolation (always escaped), star icons via @for over class names. No [innerHTML] remains.
  • about.component.spec.ts: test asserting an <iframe src="javascript:..."> comment is passed to the gallery as plain text, not a trusted SafeHtml.

models/feedback.ts / POST /api/Feedbacks are intentionally untouched (persistedXssFeedbackChallenge).

Resolved or fixed issue: none

AI Tool Disclosure

  • My contribution does not include any AI-generated content
  • My contribution includes AI-generated content, as disclosed below:
    • AI Tools: Devin
    • LLMs and versions: Devin (Cognition AI)
    • Prompts: Fix stored XSS via bypassSecurityTrustHtml on About page feedback comments and open a PR

Affirmation

Devin-Org: engineering


Devin Review

Render feedback comments as interpolated text instead of concatenating them into an HTML string passed through bypassSecurityTrustHtml and bound to [innerHTML].

Signed-off-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

Devin Review

Comment on lines +127 to +139
it('should pass feedback comments to the gallery as plain text without marking them as trusted HTML', () => {
feedbackService.find.mockReturnValue(of([{ comment: '<iframe src="javascript:alert(`xss`)">', rating: 2 }]))
component.galleryRef = { addImage: vi.fn().mockName('GalleryRef.addImage') } as any

component.populateSlideshowFromFeedbacks()

expect(component.galleryRef.addImage).toHaveBeenCalledWith({
src: 'assets/public/images/carousel/1.jpg',
args: {
comment: '<iframe src="javascript:alert(`xss`)">',
stars: ['fas fa-star', 'fas fa-star', 'far fa-star', 'far fa-star', 'far fa-star']
}
})

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🔍 Regression test skips rendering sink

The test checks addImage arguments but never renders the feedback template. Restoring [innerHTML] would leave this assertion passing.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

0 participants