Skip to content

Commit 97eeb45

Browse files
alan-agius4alxhub
authored andcommitted
fix(core): validate security-sensitive attributes in i18n bindings (#68468)
Ensures that security-sensitive attributes (e.g., sandbox, allow) are correctly validated when applied through i18n-* dynamic attribute bindings, preventing potential policy bypasses. Closes #68418 PR Close #68468
1 parent b9ec542 commit 97eeb45

2 files changed

Lines changed: 53 additions & 2 deletions

File tree

packages/core/src/render3/i18n/i18n_parse.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ import {
1717
} from '../../sanitization/html_sanitizer';
1818
import {getInertBodyHelper} from '../../sanitization/inert_body';
1919
import {_sanitizeUrl} from '../../sanitization/url_sanitizer';
20+
import {
21+
ɵɵvalidateAttribute as _validateAttribute,
22+
SECURITY_SENSITIVE_ELEMENTS,
23+
} from '../../sanitization/sanitization';
2024
import {
2125
assertDefined,
2226
assertEqual,
@@ -388,7 +392,7 @@ export function i18nAttributesFirstPass(tView: TView, index: number, values: str
388392
previousElementIndex,
389393
attrName,
390394
countBindings(updateOpCodes),
391-
SENSITIVE_ATTRS[attrName.toLowerCase()] ? _sanitizeUrl : null,
395+
i18nSanitizeAttribute(attrName),
392396
);
393397
}
394398
}
@@ -816,7 +820,7 @@ function walkIcuTree(
816820
newIndex,
817821
attr.name,
818822
0,
819-
SENSITIVE_ATTRS[lowerAttrName] ? _sanitizeUrl : null,
823+
i18nSanitizeAttribute(lowerAttrName),
820824
);
821825
} else {
822826
ngDevMode &&
@@ -969,3 +973,31 @@ function addCreateAttribute(
969973
) {
970974
create.push((newIndex << IcuCreateOpCode.SHIFT_REF) | IcuCreateOpCode.Attr, attrName, attrValue);
971975
}
976+
977+
/**
978+
* Caches all keys of `SECURITY_SENSITIVE_ELEMENTS` in a Set to avoid recomputing
979+
* or scanning them on every invocation.
980+
*/
981+
const SECURITY_SENSITIVE_ATTRS: ReadonlySet<string> = /* @__PURE__ */ (() =>
982+
new Set(
983+
Object.values(SECURITY_SENSITIVE_ELEMENTS).flatMap((attrs) => (attrs ? [...attrs.keys()] : [])),
984+
))();
985+
986+
/**
987+
* Returns a sanitizer for the given attribute name or null if the attribute is not security sensitive.
988+
*
989+
* @param attrName The name of the attribute to sanitize.
990+
* @returns The sanitizer for the given attribute name.
991+
*/
992+
function i18nSanitizeAttribute(attrName: string): SanitizerFn | null {
993+
const lowerAttrName = attrName.toLowerCase();
994+
if (SENSITIVE_ATTRS[lowerAttrName]) {
995+
return _sanitizeUrl;
996+
}
997+
998+
if (SECURITY_SENSITIVE_ATTRS.has(lowerAttrName)) {
999+
return _validateAttribute;
1000+
}
1001+
1002+
return null;
1003+
}

packages/core/test/acceptance/security_spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,25 @@ describe('iframe processing', () => {
298298
});
299299
});
300300

301+
it('should error when a translated security-sensitive attribute contains bindings', () => {
302+
@Component({
303+
selector: 'my-comp',
304+
template: `
305+
<iframe
306+
src="${TEST_IFRAME_URL}"
307+
i18n-sandbox
308+
sandbox="allow-forms {{ extraPrivileges }}"
309+
>
310+
</iframe>
311+
`,
312+
})
313+
class IframeComp {
314+
extraPrivileges = 'allow-scripts allow-same-origin';
315+
}
316+
317+
expectIframeCreationToFail(IframeComp);
318+
});
319+
301320
it('should work when a directive sets a security-sensitive attribute as a static attribute', () => {
302321
@Directive({
303322
selector: '[dir]',

0 commit comments

Comments
 (0)