javascript / expert
Snippet
Sanitized Dynamic Class Binding Against Style Injection
When a class name is derived from user-controlled or backend-driven data (e.g. a CMS field or query parameter forwarded into a `theme` input), binding it directly via `[class]="userValue"` lets an attacker inject arbitrary class names that may collide with unrelated selectors elsewhere in the app, or — combined with a loose global stylesheet — be leveraged for CSS-based data exfiltration (attribute selectors that trigger background-image requests). Angular does not sanitize class bindings the way it sanitizes `innerHTML` or `href`, because a class name isn't inherently active content, so the allowlist check here is the actual security boundary, not a redundant one.
snippet.js
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const ALLOWED_THEME_CLASSES = new Set(['theme-default', 'theme-compact', 'theme-highcontrast']);@Component({selector: 'app-themed-card',standalone: true,template: `<div [class]="resolvedClass()"><ng-content /></div>`,})export class ThemedCardComponent {readonly themeInput = input<string>('theme-default', { alias: 'theme' });readonly resolvedClass = computed(() => {const requested = this.themeInput().trim();if (!ALLOWED_THEME_CLASSES.has(requested)) {console.warn(`Rejected unrecognized theme class "${requested}"; falling back to default.`);return 'theme-default';}return requested;});}
angular
Breakdown
1
const ALLOWED_THEME_CLASSES = new Set(['theme-default', 'theme-compact', 'theme-highcontrast']);
An explicit allowlist defined at module scope — the only classes this component will ever render, regardless of what's passed in.
2
if (!ALLOWED_THEME_CLASSES.has(requested)) { ... return 'theme-default'; }
Fails closed to a safe default rather than passing unrecognized input through, so an unexpected value degrades gracefully instead of becoming an injection vector.
3
console.warn(`Rejected unrecognized theme class "${requested}"; falling back to default.`)
Logs the rejection so a legitimate new theme that was never added to the allowlist is discoverable in production logs rather than silently misrendering.