Skip to content

Commit b682c62

Browse files
alan-agius4pkozlowski-opensource
authored andcommitted
fix(core): treat object[data] as resource URL context (#67797)
Previously, the `data` attribute of the `<object>` tag was being sanitized as a regular URL instead of a `ResourceURL`, which is security-sensitive. This commit updates the runtime sanitization logic to correctly identify `object[data]` as a `ResourceURL` context. Additionally, the sanitizer lookup logic has been refactored to use a more efficient lookup map (`RESOURCE_MAP`) instead of multiple `Set` lookups, providing better performance and maintainability. Added tests to verify the correct sanitization of `object[data]` and its behavior with trusted values. PR Close #67797
1 parent fea25d1 commit b682c62

3 files changed

Lines changed: 37 additions & 24 deletions

File tree

packages/core/src/sanitization/sanitization.ts

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,16 @@ export function ɵɵtrustConstantResourceUrl(url: TemplateStringsArray): Trusted
214214
}
215215

216216
// Define sets outside the function for O(1) lookups and memory efficiency
217-
const SRC_RESOURCE_TAGS = new Set(['embed', 'frame', 'iframe', 'media', 'script']);
218-
const HREF_RESOURCE_TAGS = new Set(['base', 'link', 'script']);
217+
const RESOURCE_MAP: Record<string, Record<string, true | undefined> | undefined> = {
218+
embed: {src: true},
219+
frame: {src: true},
220+
iframe: {src: true},
221+
media: {src: true},
222+
script: {src: true, href: true, 'xlink:href': true},
223+
base: {href: true},
224+
link: {href: true},
225+
object: {data: true, codebase: true},
226+
};
219227

220228
/**
221229
* Detects which sanitizer to use for URL property, based on tag name and prop name.
@@ -225,10 +233,7 @@ const HREF_RESOURCE_TAGS = new Set(['base', 'link', 'script']);
225233
* If tag and prop names don't match Resource URL schema, use URL sanitizer.
226234
*/
227235
export function getUrlSanitizer(tag: string, prop: string) {
228-
const isResource =
229-
(prop === 'src' && SRC_RESOURCE_TAGS.has(tag)) ||
230-
(prop === 'href' && HREF_RESOURCE_TAGS.has(tag)) ||
231-
(prop === 'xlink:href' && tag === 'script');
236+
const isResource = RESOURCE_MAP[tag]?.[prop] === true;
232237

233238
return isResource ? ɵɵsanitizeResourceUrl : ɵɵsanitizeUrl;
234239
}
@@ -277,25 +282,23 @@ function getSanitizer(): Sanitizer | null {
277282
return lView && lView[ENVIRONMENT].sanitizer;
278283
}
279284

280-
const attributeName: ReadonlySet<string> = new Set(['attributename']);
281-
282285
/**
283286
* @remarks Keep this in sync with DOM Security Schema.
284287
* @see [SECURITY_SCHEMA](../../../compiler/src/schema/dom_security_schema.ts)
285288
*/
286-
const SECURITY_SENSITIVE_ELEMENTS: Readonly<Record<string, ReadonlySet<string>>> = {
287-
'iframe': new Set([
288-
'sandbox',
289-
'allow',
290-
'allowfullscreen',
291-
'referrerpolicy',
292-
'csp',
293-
'fetchpriority',
294-
]),
295-
'animate': attributeName,
296-
'set': attributeName,
297-
'animatemotion': attributeName,
298-
'animatetransform': attributeName,
289+
const SECURITY_SENSITIVE_ELEMENTS: Record<string, Record<string, true | undefined> | undefined> = {
290+
iframe: {
291+
sandbox: true,
292+
allow: true,
293+
allowfullscreen: true,
294+
referrerpolicy: true,
295+
csp: true,
296+
fetchpriority: true,
297+
},
298+
animate: {attributename: true},
299+
set: {attributename: true},
300+
animatemotion: {attributename: true},
301+
animatetransform: {attributename: true},
299302
};
300303

301304
/**
@@ -312,7 +315,7 @@ export function ɵɵvalidateAttribute(
312315
): unknown {
313316
const lowerCaseTagName = tagName.toLowerCase();
314317
const lowerCaseAttrName = attributeName.toLowerCase();
315-
if (!SECURITY_SENSITIVE_ELEMENTS[lowerCaseTagName]?.has(lowerCaseAttrName)) {
318+
if (!SECURITY_SENSITIVE_ELEMENTS[lowerCaseTagName]?.[lowerCaseAttrName]) {
316319
return value;
317320
}
318321

packages/core/test/bundling/router/bundle.golden_symbols.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@
119119
"HEADER_OFFSET",
120120
"HOST",
121121
"HOST_ATTR",
122-
"HREF_RESOURCE_TAGS",
123122
"HYDRATION",
124123
"HistoryStateManager",
125124
"HostAttributeToken",
@@ -240,6 +239,7 @@
240239
"REMOVE_STYLES_ON_COMPONENT_DESTROY_DEFAULT",
241240
"RENDERER",
242241
"REQUIRED_UNSET_VALUE",
242+
"RESOURCE_MAP",
243243
"ROUTER_CONFIGURATION",
244244
"ROUTER_OUTLET_DATA",
245245
"ROUTER_PRELOADER",
@@ -279,7 +279,6 @@
279279
"SIGNAL",
280280
"SIGNAL_NODE",
281281
"SIMPLE_CHANGES_STORE",
282-
"SRC_RESOURCE_TAGS",
283282
"STABILITY_WARNING_THRESHOLD",
284283
"SVG_NAMESPACE",
285284
"SafeSubscriber",

packages/core/test/sanitization/sanitization_spec.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ describe('sanitization', () => {
137137
expect(() => ɵɵsanitizeUrlOrResourceUrl('javascript:true', 'iframe', 'src')).toThrowError(
138138
ERROR,
139139
);
140+
expect(() => ɵɵsanitizeUrlOrResourceUrl('http://server', 'object', 'data')).toThrowError(ERROR);
141+
expect(() => ɵɵsanitizeUrlOrResourceUrl('javascript:true', 'object', 'data')).toThrowError(
142+
ERROR,
143+
);
140144
expect(() =>
141145
ɵɵsanitizeUrlOrResourceUrl(bypassSanitizationTrustHtml('javascript:true'), 'iframe', 'src'),
142146
).toThrowError(/Required a safe ResourceURL, got a HTML/);
@@ -147,6 +151,13 @@ describe('sanitization', () => {
147151
'src',
148152
).toString(),
149153
).toEqual('javascript:true');
154+
expect(
155+
ɵɵsanitizeUrlOrResourceUrl(
156+
bypassSanitizationTrustResourceUrl('javascript:true'),
157+
'object',
158+
'data',
159+
).toString(),
160+
).toEqual('javascript:true');
150161
});
151162

152163
it('should sanitize urls via sanitizeUrlOrResourceUrl', () => {

0 commit comments

Comments
 (0)