Harden Angular applications against common web vulnerabilities. Use when reviewing or improving app security, preventing XSS, configuring Content Security Policy (CSP) or Trusted Types, working with sanitization / DomSanitizer / bypassSecurityTrust, securing HttpClient (XSRF/CSRF, XSSI), auth and token storage, SSR/SSRF (allowedHosts), or keeping dependencies patched.
Installs into .claude/skills of the current project.
Are you the author of Ng Security?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/l-x-t-ng-security)
---
name: ng-security
description: Harden Angular applications against common web vulnerabilities. Use when reviewing or improving app security, preventing XSS, configuring Content Security Policy (CSP) or Trusted Types, working with sanitization / DomSanitizer / bypassSecurityTrust, securing HttpClient (XSRF/CSRF, XSSI), auth and token storage, SSR/SSRF (allowedHosts), or keeping dependencies patched.
license: MIT
metadata:
author: Alexander Thalhammer
version: '1.0'
---
# Angular Security
Security is **defense in depth**. Angular ships strong defaults (auto-sanitization, AOT, XSRF, SSRF header validation) – your job is to **not undermine them** and to add the layers Angular can't provide (CSP, Trusted Types, server-side authorization, patched dependencies). The browser is hostile territory: every value is untrusted, and any access decision that matters must be re-confirmed on the server.
## Quick checklist
- [ ] **Patch dependencies** frequently – Angular and all npm packages, at least twice a year.
- [ ] **Never concatenate user input into templates** (client or server). Keep AOT; no runtime/JIT template generation.
- [ ] **Bind through templates** (`{{ }}`, property bindings). Avoid raw DOM (`innerHTML`, `ElementRef.nativeElement`, `document`, jQuery-style libs).
- [ ] **`bypassSecurityTrust*` is a last resort** – prefer allowlisting; treat every call as an audit point.
- [ ] **Set up a strict CSP** (nonce-based); fall back to `autoCsp` if you can't.
- [ ] **Enable Trusted Types**.
- [ ] **Enforce authorization server-side** – route guards and hidden UI are UX, not security.
- [ ] **Configure SSR `allowedHosts`**; never trust `X-Forwarded-*` blindly.
## 1. Trust Angular's sanitization – don't undermine it
Angular treats all values as untrusted and auto-escapes/sanitizes anything inserted into the DOM via templates. Interpolation always escapes; `[innerHTML]` strips dangerous elements (e.g. `<script>`) before rendering.
Four **security contexts**, each sanitized differently:
| Context | Example binding | Notes |
| ---------------- | ------------------------------ | -------------------------------------- |
| HTML | `[innerHTML]` | sanitized |
| Style | `[style]` | sanitized |
| URL | `[href]`, `[src]` | sanitized |
| **Resource URL** | `<script src>`, `<iframe src>` | **cannot** be sanitized – it _is_ code |
Rules:
- **Never build templates from user input** – concatenating user data with template syntax is code injection. This applies to server-side templating too: use auto-escaping server templates and pass data, never assemble Angular templates as strings.
- Keep **AOT** (the CLI default) in production; dynamic JIT template generation with user data bypasses Angular's protections.
- **Avoid direct DOM manipulation.** Browser DOM APIs, `ElementRef.nativeElement`, and third-party libraries have no auto-sanitization. When unavoidable, sanitize explicitly:
```typescript
private sanitizer = inject(DomSanitizer);
const safe = this.sanitizer.sanitize(SecurityContext.HTML, untrustedHtml);
```
## 2. `bypassSecurityTrust*` is a last resort
The five methods – `bypassSecurityTrustHtml`, `…Script`, `…Style`, `…Url`, `…ResourceUrl` – disable sanitization for one context. They are marked security-sensitive: **every call is an audit point.**
- **Prefer explicit allowlisting** over a blanket bypass: validate the protocol (`https:` only), restrict iframe/image sources to known domains, reject everything else.
- If you must bypass, construct the trusted value **as close to the input as possible** so audits are easy.
```typescript
// Trusted resource URL for a known-safe embed
updateVideoUrl(id: string) {
const url = `https://www.youtube.com/embed/${encodeURIComponent(id)}`;
this.videoUrl = this.sanitizer.bypassSecurityTrustResourceUrl(url);
}
```
## 3. Content Security Policy (CSP)
A properly configured CSP blocks the effect of XSS **even when a bug already exists** – the most important defense-in-depth layer. Angular generates scripts/styles at runtime, so use a per-request **nonce** instead of `unsafe-inline`.
Recommended minimal policy (served as a response header):
```
default-src 'self';
script-src 'self' 'nonce-<dynamic>';
style-src 'self' 'nonce-<dynamic>';
img-src 'self' https:;
connect-src 'self' https://api.yourdomain.com;
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
require-trusted-types-for 'script';
```
Wire the nonce into Angular one of three ways:
- `"autoCsp": true` in `angular.json` (also the **fallback** when you can't manage nonces yourself),
- `<app ngCspNonce="…">` attribute, or
- the `CSP_NONCE` injection token (not compatible with critical-CSS inlining – use `autoCsp`/`ngCspNonce` instead):
```typescript
bootstrapApplication(App, {
providers: [{ provide: CSP_NONCE, useValue: globalThis.myRandomNonce }],
});
```
Rules: nonces must be **unique and unpredictable per request** (a CDN that caches the response freezes the nonce – generate it at the edge). **Never** use `unsafe-inline` or `unsafe-eval`.
## 4. Trusted Types
A browser feature that rejects unsafe strings flowing into `innerHTML`, `eval`, etc. – it holds **even if sanitization is bypassed in code**, and simplifies auditing. Enable it via response header:
```
Content-Security-Policy: trusted-types angular angular#bundler; require-trusted-types-for 'script';
```
Add policies as needed: `angular#unsafe-bypass` (for `bypassSecurityTrust*`), `angular#unsafe-jit` (JIT), `angular#unsafe-upgrade` (AngularJS hybrid). Set the header in production serving, in `ng serve` via `headers` in `angular.json`, and in your unit-test runner's server headers (e.g. `customHeaders` in `karma.config.js` for Karma setups). Unsupported browsers fall back to `DomSanitizer`.
## 5. Keep dependencies patched
Failing to update is not neutral – it's growing exposure to known CVEs.
- **Update Angular and all npm packages frequently – at least twice a year.** Angular only supports a limited number of older versions.
- Run `pnpm audit`; enable **Dependabot** / Snyk / OWASP Dependency-Check; watch GitHub Security Advisories and apply security fixes immediately.
- **Don't fork or customize Angular** – you'll miss security patches; contribute upstream instead. Avoid any API documented as a "Security Risk".
- Stay alert to supply-chain attacks (malicious package versions / compromised maintainer credentials).
- Report Angular vulnerabilities via Google's [OSS VRP](https://bughunters.google.com).
## 6. Authentication & authorization
The frontend is an insecure storage location; **every access decision must be confirmed server-side – otherwise it's only the illusion of security.**
- Route guards and hidden UI elements improve UX but are **not** access control. Enforce authorization on the API for every request.
- **Token storage trade-offs:** `localStorage`/`sessionStorage` is readable by any XSS; `HttpOnly` + `Secure` + `SameSite` cookies resist XSS but invite CSRF (mitigate with XSRF protection below). Store only short-lived tokens/session ids in the browser; keep long-lived secrets on the backend.
## 7. HttpClient: XSRF and XSSI
**XSRF/CSRF** – `HttpClient` reads a token from the `XSRF-TOKEN` cookie and sends it as the `X-XSRF-TOKEN` header on mutating, same-origin requests. The backend must set the cookie and verify the header. Customize or disable:
```typescript
provideHttpClient(
withXsrfConfiguration({ cookieName: 'CUSTOM_XSRF_TOKEN', headerName: 'X-Custom-Xsrf' }),
// or: withNoXsrfProtection()
);
```
**XSSI** – servers should prefix JSON responses with `)]}',\n` to make them non-executable; `HttpClient` strips this automatically before parsing.
## 8. Server-Side Rendering & SSRF
- **Configure allowed hosts** to block host-header injection / SSRF – never use the `*` wildcard unless another layer validates hosts:
```json
{ "options": { "security": { "allowedHosts": ["example.com", "*.example.com"] } } }
```
(Node: `NG_ALLOWED_HOSTS`.) Angular treats all `X-Forwarded-*` headers as untrusted by default; only enable `trustProxyHeaders` when behind a proxy that strictly validates/overrides them.
- **Isolate per-request state** – never share a global injector or mutable state across parallel SSR requests, or one user's tokens/content can leak into another's response.
## Reviewing an app
Grep for the audit points and verify each: `bypassSecurityTrust`, `innerHTML`, `ElementRef`, `nativeElement`, `document.`, `eval`, server-side template strings, and missing CSP/Trusted Types headers. Report each audit point as pass/fail with `file:line` evidence.