Skip to content

fix(compiler): sanitize href and xlink:href bindings on SVG use elements - #71619

Open
SkyZeroZx wants to merge 2 commits into
angular:mainfrom
SkyZeroZx:fix/core-svg-use-resource-url
Open

SkyZeroZx wants to merge 2 commits into
angular:mainfrom
SkyZeroZx:fix/core-svg-use-resource-url

Conversation

@SkyZeroZx

Copy link
Copy Markdown
Contributor

<use> clones the referenced document into the page, but both attributes had no security context, so a bound value was written as is. They are now RESOURCE_URL, like iframe[src].

Dynamic <use> bindings, null included, now throw NG0904 unless the value is marked with bypassSecurityTrustResourceUrl. Static values like href="#icon" are unaffected.

Fixes #71618

@angular-robot angular-robot Bot added area: compiler Issues related to `ngc`, Angular's template compiler area: devtools labels Oct 26, 2026
@ngbot ngbot Bot added this to the Backlog milestone Oct 26, 2026
selector: 'ng-icon',
template: `
<svg class="svg">
<use [attr.xlink:href]="'/assets/sprite.svg#' + name()"></use>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I was thinking about allowing #[whatever_here_icon], but I think it's better to keep it consistent with iframe[src].

Since Angular selects the sanitizer based on the element and attribute, not the URL value.
Even <iframe [src]="'#foo'"> throws today, so doing an exception just for <use> doesn't seem worth it

Also, # isn't completely safe because cloned SVG content can still have javascript: links that execute on click.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This change is quite breaking for existing Angular applications because dynamic <use> bindings are heavily used for SVG icon components (as seen right here in DevTools).

For # fragment references specifically, a #fragment only clones an element that is already in the current document's DOM and never fetches an external resource. If an attacker were able to inject an unsanitized <a href="javascript:..."> into the current document, XSS would already be present without <use>.

Since setElementAttribute and elementPropertyInternal already pass (value, tagName, propName) to SanitizerFn (similar to ɵɵsanitizeHtml), ɵɵsanitizeResourceUrl (and ɵɵsanitizeUrlOrResourceUrl) can allow #... fragment strings when the target is <svg:use> without needing any compiler changes. Because <use [attr.href]="'#' + icon"> is so common for inline SVG sprites, we should allow #... references to reduce the breaking impact rather than forcing developers to wrap them in bypassSecurityTrustResourceUrl.

@JeanMeche JeanMeche added the action: global presubmit The PR is in need of a google3 global presubmit label Oct 26, 2026
@JeanMeche
JeanMeche requested review from JeanMeche and alan-agius4 and removed request for alan-agius4 and atscott October 26, 2026 16:56
@alan-agius4 alan-agius4 added breaking changes target: major This PR is targeted for the next major release labels Oct 27, 2026

@alan-agius4 alan-agius4 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This change is quite breaking in practice because dynamic <svg><use> bindings are widely used across Angular applications and UI libraries for SVG icons (as demonstrated by Angular DevTools breaking in this PR). To minimize unnecessary breakage and prepare this properly:

  1. Allow same document fragment references (#...) on <use>:
    Binding dynamic fragment identifiers (e.g. <use [attr.href]="'#' + icon">) is a very common pattern for inline SVG sprites. Unlike document URLs, a #fragment reference never fetches an external resource and only targets an element already present in the current DOM. Since SanitizerFn already receives (value, tagName, propName) at runtime (like ɵɵsanitizeHtml), ɵɵsanitizeResourceUrl and ɵɵsanitizeUrlOrResourceUrl can allow #... values on <use>. This also keeps the existing #id test in packages/core/test/linker/integration_spec.ts passing without changing it to xlink:title.

  2. Expand test coverage in packages/core/test/acceptance/security_spec.ts:
    Please also add tests covering [attr.xlink:href] rejecting untrusted document URLs, allowing values wrapped with bypassSecurityTrustResourceUrl, and allowing #fragment values.

  3. Add a BREAKING CHANGE: footer to the commit message:
    Since dynamic <use> bindings that reference an external or same origin document path (like [attr.xlink:href]="'/assets/sprite.svg#' + name()") will now throw NG0904 unless wrapped in bypassSecurityTrustResourceUrl, this is a significant breaking change. Please include a BREAKING CHANGE: note in the commit message body so this can be targeted for a major release.

@pullapprove
pullapprove Bot requested a review from alan-agius4 October 27, 2026 09:56
@SkyZeroZx
SkyZeroZx force-pushed the fix/core-svg-use-resource-url branch from 2311458 to 1274311 Compare October 27, 2026 16:08
@angular-robot angular-robot Bot added the detected: breaking change PR contains a commit with a breaking change label Oct 27, 2026
@JeanMeche JeanMeche modified the milestones: Backlog, v23 candidates Oct 27, 2026
@SkyZeroZx
SkyZeroZx force-pushed the fix/core-svg-use-resource-url branch from 1274311 to 7b94914 Compare October 27, 2026 16:43
@SkyZeroZx

SkyZeroZx commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor Author

I'm wondering if this will only land in v23 or if it could be backported, considering that v23 is planned for next year, so LTS v20 , 21 and 22 would never get it.

I think it can be an XSS vector for some users on the LTS versions, since both routes from #71618 affect browsers that are inside the baseline for those versions.

Route A only needs a data: URL in a field the app binds, and it still runs on Firefox ESR 115, which Mozilla still ships (I tested on 115.44 with 21.2.25). Route B runs even on the current Firefox 159, with attachment and nosniff on the response.

This reminds me a bit of GHSA-v4hv-rgfq-gp49, where xlink:href on SVG links was also missing from the schema, and it was backported to LTS, even with a hard error for the attributeName bindings. Also, with the # allowance, only references to another document break now.


// `use` references are resource URLs. The sprite is an asset of DevTools itself and only the
// icon name varies, so the reference is marked as trusted here.
protected readonly href = computed(() =>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we still need the bypass here ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Because we are using /assets/sprite.svg#${this.name()} which points to an assets URL and according to the above comment:

Binding dynamic fragment identifiers (e.g. <use [attr.href]="'#' + icon">) is a very common pattern for inline SVG sprites. Unlike document URLs, a #fragment reference never fetches an external resource and only targets an element already present in the current DOM.

The only exception will be when it starts with #

@SkyZeroZx
SkyZeroZx requested a review from JeanMeche November 2, 2026 16:20
`<use>` clones the referenced document into the page, but both attributes had no security context, so a bound value was written as is. They are now `RESOURCE_URL`, like `iframe[src]`.

A reference that cannot pull in a document is still allowed: a nullish value, which removes the attribute, and a same document fragment like `#icon`, which is how inline SVG sprites are referenced. Any other dynamic value throws NG0904 unless it is marked with `bypassSecurityTrustResourceUrl`. Static values like `href="https://github.com/assets/sprite.svg#icon"` are unaffected.

Fixes angular#71618

BREAKING CHANGE: `href` and `xlink:href` bindings on `<svg:use>` are now sanitized as resource URLs. A binding that references another document, such as `[attr.xlink:href]="'/assets/sprite.svg#' + name()"`, throws NG0904 unless the value is marked with `DomSanitizer.bypassSecurityTrustResourceUrl`. Fragment references like `[attr.href]="'#' + icon"` and static attributes are unaffected.
`<use>` hrefs are now sanitized as resource URLs, so the icon marks its constant sprite path with `bypassSecurityTrustResourceUrl`.
@SkyZeroZx
SkyZeroZx force-pushed the fix/core-svg-use-resource-url branch from 7b94914 to 864bb95 Compare November 11, 2026 01:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action: global presubmit The PR is in need of a google3 global presubmit area: compiler Issues related to `ngc`, Angular's template compiler area: devtools breaking changes detected: breaking change PR contains a commit with a breaking change target: major This PR is targeted for the next major release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SVG <use> href bindings bypass sanitization allowing XSS

3 participants