diff --git a/packages/@glimmer-workspace/integration-tests/test/attributes-test.ts b/packages/@glimmer-workspace/integration-tests/test/attributes-test.ts index eef13f25c13..51ea7d96fba 100644 --- a/packages/@glimmer-workspace/integration-tests/test/attributes-test.ts +++ b/packages/@glimmer-workspace/integration-tests/test/attributes-test.ts @@ -144,6 +144,25 @@ export class AttributesTests extends RenderTest { this.assertStableNodes(); } + @test + 'svg a[href] is marked as unsafe (sanitization is not namespace-dependent)'() { + // SVG-namespaced elements preserve their authored (lowercase) tag name, so + // `element.tagName` is `'a'` rather than the `'A'` an HTML anchor reports. + // URL sanitization must still apply, otherwise `` is an XSS + // bypass for the same `javascript:` URLs that are blocked on ``. + this.render('', { foo: 'javascript:foo()' }); + this.assertHTML(''); + this.assertStableRerender(); + + this.rerender({ foo: 'http://foo.bar' }); + this.assertHTML(''); + this.assertStableNodes(); + + this.rerender({ foo: 'javascript:foo()' }); + this.assertHTML(''); + this.assertStableNodes(); + } + @test 'triple curlies in attribute position'() { this.render('
Hello
', { diff --git a/packages/@glimmer/runtime/lib/dom/sanitized-values.ts b/packages/@glimmer/runtime/lib/dom/sanitized-values.ts index a5203749453..9c4dc5a67aa 100644 --- a/packages/@glimmer/runtime/lib/dom/sanitized-values.ts +++ b/packages/@glimmer/runtime/lib/dom/sanitized-values.ts @@ -26,7 +26,14 @@ function checkDataURI(tagName: Nullable, attribute: string): boolean { } export function requiresSanitization(tagName: string, attribute: string): boolean { - return checkURI(tagName, attribute) || checkDataURI(tagName, attribute); + // `badTags`/`badTagsForDataURI` are listed in the uppercase form that HTML + // elements report from `tagName`. Elements in other namespaces (e.g. an SVG + // ``) preserve their authored case, so `element.tagName` is `'a'` + // rather than `'A'`. Normalize here so the build-time gate matches what + // `sanitizeAttributeValue` already does, and the URL sanitization control + // can't be bypassed simply by living in the SVG namespace. + const upperTagName = tagName.toUpperCase(); + return checkURI(upperTagName, attribute) || checkDataURI(upperTagName, attribute); } interface NodeUrlParseResult {