Skip to content

Fix stored XSS in “Save as HTML” exports by escaping templated fields#5808

Closed
Chaitu7032 wants to merge 4 commits intosugarlabs:masterfrom
Chaitu7032:XSS
Closed

Fix stored XSS in “Save as HTML” exports by escaping templated fields#5808
Chaitu7032 wants to merge 4 commits intosugarlabs:masterfrom
Chaitu7032:XSS

Conversation

@Chaitu7032
Copy link
Contributor

@Chaitu7032 Chaitu7032 commented Feb 19, 2026

Summary

**This PR fixes a stored XSS vulnerability in the “Save as HTML” export feature.

Previously, user-controlled project fields (project name, description, exported data, and image URL) were inserted into an HTML template without escaping. A malicious project could inject markup or scripts that would execute when someone opened the exported .html file.

The export path now safely escapes user-controlled values and sanitizes image URLs so exported HTML files cannot execute injected code. **

What was happening before

The HTML exporter builds a full document by taking a template string and directly replacing placeholders like:

{{ project_name }} {{ project_description }} {{ data }} {{ project_image }}

Because these values were inserted without escaping, a crafted project name such as:

</title><img src=x onerror=alert(1)><title>

could break out of the <title> element and inject a new tag with JavaScript execution.

Impact:

Anyone who receives and opens an exported .html file could have script executed in their browser.
This is especially risky because exporting and sharing projects is a normal workflow.

What this PR changes

The exporter now treats all user-controlled values as text, not HTML.

In SaveInterface.js, prepareHTML() now:

  • Escapes HTML special characters in:

    • project name
    • project description
    • exported project data
      (& < > " ' → escaped entities)
  • Sanitizes the project image URL before inserting it:
    - Allows http:// and https://
    - Allows data:image/*;base64,...
    - Allows relative URLs
    - Rejects other schemes (e.g. javascript:)

  • Uses function replacements (.replace(..., () => value)) so escaping is applied consistently to every placeholder occurrence.

Behavior after the fix

If a project name or description contains HTML (malicious or accidental), it is exported as visible text, not interpreted as markup.

Example repro payload:
</title><img src=x onerror=alert(1)><title>

After the fix:

  • No script runs
  • No alert appears
  • The exported HTML contains escaped text such as &lt;img ...&gt;
  • Exported project data ({{ data }}) is also rendered as text and cannot inject <script> tags.

Tests / Verification

Added a regression test that uses the exact exploit payload and verifies it is escaped and not executable:

SaveInterface.test.js (XSS regression coverage)

Verified locally with:
npx jest SaveInterface.test.js --runInBand

image

How to test manually

  • Set project name to:

</title><img src=x onerror=alert(1)><title>

  • Use Save → Save as HTML
  • Open the downloaded .html file in a browser

Expected result:

  • No alert executes
  • The page loads normally
  • The HTML source shows escaped entities (e.g. &lt;img ...&gt;)

Security notes

  • Escaping at the templating boundary is the safest minimal fix:
  • Preserves the existing template-string approach
  • Prevents HTML and attribute injection
  • Avoids introducing new dependencies or breaking behavior

Image URL sanitization further reduces risk from attribute injection and unsafe schemes.

@github-actions
Copy link
Contributor

✅ All Jest tests passed! This PR is ready to merge.

@github-actions
Copy link
Contributor

✅ All Jest tests passed! This PR is ready to merge.

@Chaitu7032
Copy link
Contributor Author

@walterbender sir .. I request to please have a look when you have time ..

@Commanderk3
Copy link
Member

@Chaitu7032 @walterbender
I found the import to be broken. Please check this :

pr-5808.mp4

@Chaitu7032
Copy link
Contributor Author

Chaitu7032 commented Feb 20, 2026

@Commanderk3 thanks for testing I will go through it, thankyou for taking look into it ..

@Chaitu7032
Copy link
Contributor Author

Chaitu7032 commented Feb 20, 2026

@Commanderk3 exported HTML stores project JSON as escaped text (e.g. "), so [JSON.parse()] was receiving strings like [[0,"star",...] and throwing [Unexpected token '&'] thanks for pointing it out .. iam giving fix for it ..
...

@Commanderk3
Copy link
Member

@Commanderk3 exported HTML stores project JSON as escaped text (e.g. "), so [JSON.parse()] was receiving strings like [[0,"star",...] and throwing [Unexpected token '&'] thanks for pointing it out ...

No worries, but please do more testing from next time👍

@github-actions
Copy link
Contributor

✅ All Jest tests passed! This PR is ready to merge.

@github-actions
Copy link
Contributor

✅ All Jest tests passed! This PR is ready to merge.

@Chaitu7032
Copy link
Contributor Author

Chaitu7032 commented Feb 20, 2026

video_20260220_165032_edit.mp4

The import project crash is solved now .. @Commanderk3 . Thanks you so much I will make sure it will not happen in future . I apologise for this..

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