fix(core): reject dynamic script host elements

The previous fix for GHSA-692r-grfm-v8x7 was incomplete because it rejected script tags only when locating an explicit host element. Dynamic component instantiation can also infer the host element from the component selector.

Move the script-host rejection to the point where ComponentFactory has resolved the host element for either path, so createComponent rejects script hosts consistently.

(cherry picked from commit 135f3755b4)
This commit is contained in:
SkyZeroZx
2026-06-28 00:15:56 -05:00
committed by Alex Rickabaugh
parent 26831d0cbd
commit 8eb7aea08b
10 changed files with 30 additions and 7 deletions
@@ -193,6 +193,15 @@ function createHostElement(componentDef: ComponentDef<unknown>, renderer: Render
return createElementNode(renderer, tagName, namespace);
}
function assertNotScriptHostElement(tagName: string | null | undefined): void {
if (tagName?.toLowerCase() === 'script') {
throw new RuntimeError(
RuntimeErrorCode.UNSAFE_VALUE_IN_SCRIPT,
ngDevMode && `"<script>" tag is not allowed as a component host element.`,
);
}
}
/**
* Infers the tag name that should be used for a component based on its definition.
* @param componentDef Definition for which to resolve the tag name.
@@ -283,6 +292,7 @@ export class ComponentFactory<T> extends AbstractComponentFactory<T> {
rootViewInjector,
)
: createHostElement(cmpDef, hostRenderer);
assertNotScriptHostElement(hostElement?.tagName);
const hasInputBindings =
componentBindings?.some(isInputBinding) ||
directives?.some((d) => typeof d !== 'function' && d.bindings.some(isInputBinding));
@@ -21,7 +21,6 @@ import {stringify} from '../../util/stringify';
import {assertFirstCreatePass, assertHasParent, assertLView} from '../assert';
import {attachPatchData} from '../context_discovery';
import {getNodeInjectable, getOrCreateNodeInjectorForNode} from '../di';
import {RuntimeError, RuntimeErrorCode} from '../../errors';
import {throwMultipleComponentError} from '../errors';
import {ComponentDef, ComponentTemplate, DirectiveDef, RenderFlags} from '../interfaces/definition';
import {
@@ -178,12 +177,6 @@ export function locateHostElement(
// projection.
const preserveContent = preserveHostContent || encapsulation === ViewEncapsulation.ShadowDom;
const rootElement = renderer.selectRootElement(elementOrSelector, preserveContent);
if (rootElement.tagName.toLowerCase() === 'script') {
throw new RuntimeError(
RuntimeErrorCode.UNSAFE_VALUE_IN_SCRIPT,
ngDevMode && `"<script>" tag is not allowed as a component host element.`,
);
}
applyRootElementTransform(rootElement as HTMLElement);
return rootElement;
}
@@ -807,6 +807,19 @@ describe('innerHTML processing', () => {
});
});
describe('Component host element validation', () => {
it('should throw an error when dynamically creating a component with a script selector', () => {
@Component({
selector: 'script',
template: '',
})
class ScriptHost {}
const environmentInjector = TestBed.inject(EnvironmentInjector);
expect(() => {
createComponent(ScriptHost, {environmentInjector});
}).toThrowError(/"<script>" tag is not allowed as a component host element/);
});
it('should throw an error when dynamically mounting a component onto a script tag', () => {
@Component({
selector: 'my-sink',
@@ -344,6 +344,7 @@
"areAnimationSupported",
"arrRemove",
"assertNotDestroyed",
"assertNotScriptHostElement",
"attachPatchData",
"balancePreviousStylesIntoKeyframes",
"balanceProperties",
@@ -315,6 +315,7 @@
"areAnimationSupported",
"arrRemove",
"assertNotDestroyed",
"assertNotScriptHostElement",
"attachPatchData",
"bind",
"bindingUpdated",
@@ -375,6 +375,7 @@
"assertAllValuesPresent",
"assertControlPresent",
"assertNotDestroyed",
"assertNotScriptHostElement",
"assertPlatform",
"attachPatchData",
"baseElement",
@@ -380,6 +380,7 @@
"assertAllValuesPresent",
"assertControlPresent",
"assertNotDestroyed",
"assertNotScriptHostElement",
"assertPlatform",
"attachPatchData",
"baseElement",
@@ -311,6 +311,7 @@
"areAnimationSupported",
"arrRemove",
"assertNotDestroyed",
"assertNotScriptHostElement",
"attachPatchData",
"baseElement",
"bind",
@@ -421,6 +421,7 @@
"arrRemove",
"arrayEquals",
"assertNotDestroyed",
"assertNotScriptHostElement",
"attachPatchData",
"baseElement",
"bind",
@@ -250,6 +250,7 @@
"areAnimationSupported",
"arrRemove",
"assertNotDestroyed",
"assertNotScriptHostElement",
"attachPatchData",
"baseElement",
"bind",