Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "fix: prevent XSS theme vulnerability during SSR",
"packageName": "@fluentui/react-provider",
"email": "martinhochel@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,17 @@ describe('createCSSRuleFromTheme', () => {
`".selector { --borderRadiusLarge: 10px; --colorBackgroundOverlay: rgba(0, 0, 0, 0.4); }"`,
);
});

it('prevents XSS by replacing angle brackets that could inject HTML', () => {
const theme = {
colorBrandBackground: '</style><script>alert("xss")</script>',
} as PartialTheme;

const result = createCSSRuleFromTheme('.selector', theme);
expect(result).not.toContain('<');
expect(result).not.toContain('>');
expect(result).toMatchInlineSnapshot(
`".selector { --colorBrandBackground: \\\\3C /style\\\\3E \\\\3C script\\\\3E alert(\\"xss\\")\\\\3C /script\\\\3E ; }"`,
);
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
import type { PartialTheme } from '@fluentui/react-theme';

const CSS_ESCAPE_MAP = {
'<': '\\3C ',
'>': '\\3E ',
};
/**
* Escapes characters that could break out of a <style> tag during SSR.
*
* IMPORTANT: Do not strip quotes. Theme values legitimately include quoted font families and other CSS.
* We only need to ensure the generated text cannot terminate the style tag and inject HTML.
*/
function escapeForStyleTag(value: string): string {
// Escape as CSS code points so the resulting CSS still represents the same characters.
// Using CSS escapes prevents the HTML parser from seeing a literal '<' / '>' and closing <style>.
return value.replace(/[<>]/g, match => CSS_ESCAPE_MAP[match as keyof typeof CSS_ESCAPE_MAP]);
}

/**
* Creates a CSS rule from a theme object.
*
Expand All @@ -11,7 +27,7 @@ export function createCSSRuleFromTheme(selector: string, theme: PartialTheme | u
return `${cssVarRule}--${cssVar}: ${theme[cssVar]}; `;
}, '');

return `${selector} { ${cssVarsAsString} }`;
return `${selector} { ${escapeForStyleTag(cssVarsAsString)} }`;
}

return `${selector} {}`;
Expand Down