fix(compiler): promote constants in templates to Trusted Types (#39211)
Angular treats constant values of attributes and properties in templates as secure. This means that these values are not sanitized, and are instead passed directly to the corresponding setAttribute or setProperty function. In cases where the given attribute or property is security-sensitive, this causes a Trusted Types violation. To address this, functions for promoting constant strings to each of the three Trusted Types are introduced to Angular's private codegen API. The compiler is updated to wrap constant strings with calls to these functions as appropriate when constructing the `consts` array. This is only done for security-sensitive attributes and properties, as classified by Angular's dom_security_schema. PR Close #39211
This commit is contained in:
@ -320,4 +320,9 @@ export class Identifiers {
|
||||
static sanitizeUrl: o.ExternalReference = {name: 'ɵɵsanitizeUrl', moduleName: CORE};
|
||||
static sanitizeUrlOrResourceUrl:
|
||||
o.ExternalReference = {name: 'ɵɵsanitizeUrlOrResourceUrl', moduleName: CORE};
|
||||
static trustConstantHtml: o.ExternalReference = {name: 'ɵɵtrustConstantHtml', moduleName: CORE};
|
||||
static trustConstantScript:
|
||||
o.ExternalReference = {name: 'ɵɵtrustConstantScript', moduleName: CORE};
|
||||
static trustConstantResourceUrl:
|
||||
o.ExternalReference = {name: 'ɵɵtrustConstantResourceUrl', moduleName: CORE};
|
||||
}
|
||||
|
@ -568,7 +568,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
|
||||
const nonContentSelectAttributes =
|
||||
ngContent.attributes.filter(attr => attr.name.toLowerCase() !== NG_CONTENT_SELECT_ATTR);
|
||||
const attributes = this.getAttributeExpressions(nonContentSelectAttributes, [], []);
|
||||
const attributes =
|
||||
this.getAttributeExpressions(ngContent.name, nonContentSelectAttributes, [], []);
|
||||
|
||||
if (attributes.length > 0) {
|
||||
parameters.push(o.literal(projectionSlotIdx), o.literalArr(attributes));
|
||||
@ -635,7 +636,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
|
||||
// add attributes for directive and projection matching purposes
|
||||
const attributes: o.Expression[] = this.getAttributeExpressions(
|
||||
outputAttrs, allOtherInputs, element.outputs, stylingBuilder, [], i18nAttrs);
|
||||
element.name, outputAttrs, allOtherInputs, element.outputs, stylingBuilder, [], i18nAttrs);
|
||||
parameters.push(this.addAttrsToConsts(attributes));
|
||||
|
||||
// local refs (ex.: <div #foo #bar="baz">)
|
||||
@ -867,8 +868,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
// prepare attributes parameter (including attributes used for directive matching)
|
||||
const [i18nStaticAttrs, staticAttrs] = partitionArray(template.attributes, hasI18nMeta);
|
||||
const attrsExprs: o.Expression[] = this.getAttributeExpressions(
|
||||
staticAttrs, template.inputs, template.outputs, undefined /* styles */,
|
||||
template.templateAttrs, i18nStaticAttrs);
|
||||
NG_TEMPLATE_TAG_NAME, staticAttrs, template.inputs, template.outputs,
|
||||
undefined /* styles */, template.templateAttrs, i18nStaticAttrs);
|
||||
parameters.push(this.addAttrsToConsts(attrsExprs));
|
||||
|
||||
// local refs (ex.: <ng-template #foo>)
|
||||
@ -1285,8 +1286,9 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
* because those values are intended to always be generated as property instructions.
|
||||
*/
|
||||
private getAttributeExpressions(
|
||||
renderAttributes: t.TextAttribute[], inputs: t.BoundAttribute[], outputs: t.BoundEvent[],
|
||||
styles?: StylingBuilder, templateAttrs: (t.BoundAttribute|t.TextAttribute)[] = [],
|
||||
elementName: string, renderAttributes: t.TextAttribute[], inputs: t.BoundAttribute[],
|
||||
outputs: t.BoundEvent[], styles?: StylingBuilder,
|
||||
templateAttrs: (t.BoundAttribute|t.TextAttribute)[] = [],
|
||||
i18nAttrs: (t.BoundAttribute|t.TextAttribute)[] = []): o.Expression[] {
|
||||
const alreadySeen = new Set<string>();
|
||||
const attrExprs: o.Expression[] = [];
|
||||
@ -1296,7 +1298,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
if (attr.name === NG_PROJECT_AS_ATTR_NAME) {
|
||||
ngProjectAsAttr = attr;
|
||||
}
|
||||
attrExprs.push(...getAttributeNameLiterals(attr.name), asLiteral(attr.value));
|
||||
attrExprs.push(
|
||||
...getAttributeNameLiterals(attr.name), trustedConstAttribute(elementName, attr));
|
||||
});
|
||||
|
||||
// Keep ngProjectAs next to the other name, value pairs so we can verify that we match
|
||||
@ -2128,6 +2131,20 @@ export function resolveSanitizationFn(context: core.SecurityContext, isAttribute
|
||||
}
|
||||
}
|
||||
|
||||
function trustedConstAttribute(tagName: string, attr: t.TextAttribute): o.Expression {
|
||||
const value = asLiteral(attr.value);
|
||||
switch (elementRegistry.securityContext(tagName, attr.name, /* isAttribute */ true)) {
|
||||
case core.SecurityContext.HTML:
|
||||
return o.importExpr(R3.trustConstantHtml).callFn([value], attr.valueSpan);
|
||||
case core.SecurityContext.SCRIPT:
|
||||
return o.importExpr(R3.trustConstantScript).callFn([value], attr.valueSpan);
|
||||
case core.SecurityContext.RESOURCE_URL:
|
||||
return o.importExpr(R3.trustConstantResourceUrl).callFn([value], attr.valueSpan);
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
function isSingleElementTemplate(children: t.Node[]): children is[t.Element] {
|
||||
return children.length === 1 && children[0] instanceof t.Element;
|
||||
}
|
||||
|
Reference in New Issue
Block a user