diff --git a/packages/compiler/src/render3/view/i18n/meta.ts b/packages/compiler/src/render3/view/i18n/meta.ts index f5071b3f8fc5..081c96da53c9 100644 --- a/packages/compiler/src/render3/view/i18n/meta.ts +++ b/packages/compiler/src/render3/view/i18n/meta.ts @@ -12,7 +12,10 @@ import * as i18n from '../../../i18n/i18n_ast'; import {createI18nMessageFactory, VisitNodeFn} from '../../../i18n/i18n_parser'; import * as html from '../../../ml_parser/ast'; import {ParseTreeResult} from '../../../ml_parser/parser'; +import {splitNsName} from '../../../ml_parser/tags'; import * as o from '../../../output/output_ast'; +import {SecurityContext} from '../../../core'; +import {DomElementSchemaRegistry} from '../../../schema/dom_element_schema_registry'; import {isTrustedTypesSink} from '../../../schema/trusted_types_sinks'; import {hasI18nAttrs, I18N_ATTR, I18N_ATTR_PREFIX, icuFromI18nMessage} from './util'; @@ -49,6 +52,18 @@ const setI18nRefs = (originalNodeMap: Map): VisitNodeFn => }; }; +const domSchema = new DomElementSchemaRegistry(); + +function isIframePolicyTranslatedAttribute(tagName: string, attrName: string): boolean { + const elementName = splitNsName(tagName)[1]; + + return ( + elementName.toLowerCase() === 'iframe' && + domSchema.securityContext(elementName, attrName, /* isAttribute */ true) === + SecurityContext.ATTRIBUTE_NO_BINDING + ); +} + /** * This visitor walks over HTML parse tree and converts information stored in * i18n-related attributes ("i18n" and "i18n-*") into i18n meta object that is @@ -201,14 +216,20 @@ export class I18nMetaVisitor implements html.Visitor { } else if (attr.name.startsWith(I18N_ATTR_PREFIX)) { // 'i18n-*' attributes const name = attr.name.slice(I18N_ATTR_PREFIX.length); - let isTrustedType: boolean; + let isSecuritySensitive: boolean; if (node instanceof html.Component) { - isTrustedType = node.tagName === null ? false : isTrustedTypesSink(node.tagName, name); + isSecuritySensitive = + node.tagName === null + ? false + : isTrustedTypesSink(node.tagName, name) || + isIframePolicyTranslatedAttribute(node.tagName, name); } else { - isTrustedType = isTrustedTypesSink(node.name, name); + isSecuritySensitive = + isTrustedTypesSink(node.name, name) || + isIframePolicyTranslatedAttribute(node.name, name); } - if (isTrustedType) { + if (isSecuritySensitive) { this._reportError( attr, `Translating attribute '${name}' is disallowed for security reasons.`, diff --git a/packages/core/test/acceptance/security_spec.ts b/packages/core/test/acceptance/security_spec.ts index 884fea63c168..39a52b79bdc9 100644 --- a/packages/core/test/acceptance/security_spec.ts +++ b/packages/core/test/acceptance/security_spec.ts @@ -752,7 +752,7 @@ describe('iframe processing', () => { }, ); - it('should work when a security-sensitive attributes are marked for translation', () => { + it('should error when iframe policy attributes are marked for translation', () => { @Component({ selector: 'my-comp', template: ` `, @@ -761,7 +761,9 @@ describe('iframe processing', () => { }) class IframeComp {} - expectIframeToBeCreated(IframeComp, {src: TEST_IFRAME_URL}); + expect(() => TestBed.createComponent(IframeComp)).toThrowError( + /Translating attribute 'sandbox' is disallowed for security reasons./, + ); }); }); });