🎯 What
SDSComXSL.xsl renders SupplierInformation/CompanyUrl in the generated HTML. The current markup uses a literal href="CompanyUrl", which appears to be a typo for a dynamic attribute value such as href="{CompanyUrl}".
If this is corrected without validation, untrusted SDS XML could supply a dangerous URL scheme and create a clickable XSS vector.
Vulnerable area:
<xsl:if test="CompanyUrl">
<dt class="col-4">
<h5>
<xsl:value-of select="$Section1.3-CompanyUrl"/>
</h5>
</dt>
<dd class="col-8">
<a href="CompanyUrl" target="_blank"><xsl:value-of select="CompanyUrl"/></a>
</dd>
</xsl:if>
⚠️ Risk
If a future fix changes this to href="{CompanyUrl}" directly, values such as the following could become executable or unsafe clickable links:
javascript:alert(1)
data:text/html,<script>alert(1)</script>
vbscript:...
//evil.example
- whitespace-obfuscated URL schemes
Potential impact:
- Cross-site scripting when users open transformed SDS HTML and click the company URL.
- Phishing or redirection to malicious pages.
- Reverse-tabnabbing risk because the link opens with
target="_blank" and no rel protection.
Blast radius: any user viewing HTML generated from untrusted, external, or compromised SDS XML input.
🛡️ Proposed solution
Render CompanyUrl as a clickable link only when it is clearly safe:
- Normalise whitespace.
- Allow only
http:// and https:// schemes.
- Optionally convert plain domain-style values such as
example.com to https://example.com.
- Render unsafe values as escaped text instead of a link.
- Add
rel="noopener noreferrer" when using target="_blank".
Suggested replacement:
<xsl:variable name="companyUrl" select="normalize-space(CompanyUrl[1])"/>
<xsl:variable
name="safeCompanyHref"
select="
if (matches(lower-case($companyUrl), '^https?://') and not(matches($companyUrl, '\s'))) then $companyUrl
else if (matches($companyUrl, '^[A-Za-z0-9][A-Za-z0-9.-]*\.[A-Za-z]{2,}(/.*)?$')) then concat('https://', $companyUrl)
else ''
"
/>
<xsl:choose>
<xsl:when test="$safeCompanyHref != ''">
<a href="{$safeCompanyHref}" target="_blank" rel="noopener noreferrer">
<xsl:value-of select="$companyUrl"/>
</a>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$companyUrl"/>
</xsl:otherwise>
</xsl:choose>
✅ Verification notes
Add or manually run checks confirming that unsafe schemes are not emitted as clickable href values:
javascript:alert(1) → escaped text only, no link.
data:text/html,<script>alert(1)</script> → escaped text only, no link.
//evil.example → escaped text only, no link.
https://example.com → clickable link.
http://example.com → clickable link.
example.com → clickable link as https://example.com if plain-domain support is retained.
Run the existing project checks after patching:
🧭 Implementation note
The XSLT is declared as version 3.0, so matches() and conditional expressions are appropriate for this stylesheet.
🎯 What
SDSComXSL.xslrendersSupplierInformation/CompanyUrlin the generated HTML. The current markup uses a literalhref="CompanyUrl", which appears to be a typo for a dynamic attribute value such ashref="{CompanyUrl}".If this is corrected without validation, untrusted SDS XML could supply a dangerous URL scheme and create a clickable XSS vector.
Vulnerable area:
If a future fix changes this to
href="{CompanyUrl}"directly, values such as the following could become executable or unsafe clickable links:javascript:alert(1)data:text/html,<script>alert(1)</script>vbscript:...//evil.examplePotential impact:
target="_blank"and norelprotection.Blast radius: any user viewing HTML generated from untrusted, external, or compromised SDS XML input.
🛡️ Proposed solution
Render
CompanyUrlas a clickable link only when it is clearly safe:http://andhttps://schemes.example.comtohttps://example.com.rel="noopener noreferrer"when usingtarget="_blank".Suggested replacement:
✅ Verification notes
Add or manually run checks confirming that unsafe schemes are not emitted as clickable
hrefvalues:javascript:alert(1)→ escaped text only, no link.data:text/html,<script>alert(1)</script>→ escaped text only, no link.//evil.example→ escaped text only, no link.https://example.com→ clickable link.http://example.com→ clickable link.example.com→ clickable link ashttps://example.comif plain-domain support is retained.Run the existing project checks after patching:
npm test🧭 Implementation note
The XSLT is declared as version 3.0, so
matches()and conditional expressions are appropriate for this stylesheet.