skip to content

CSP & Trusted Types

Angular ships Trusted Types policies, a CSP_NONCE token for the styles it inserts, and ahead-of-time templates that never need eval, so a strict CSP can hold. Interviewers ask what breaks and why.

part ofAngularoverview, primer and where to startread it →
on this pageshow

explore

questions

4

Why can an Angular app compiled ahead of time run under a CSP without 'unsafe-eval', while a JIT-compiled app cannot?

level: juniorimportance: should knowfreq 38%

answer

  1. where templates become code
  2. build time versus in the browser
  3. strings turned into functions
  4. eval and new Function
  5. template injection too

basics

~20 s

AOT compiles templates into JavaScript at build time, so the browser only runs ordinary bundled code. The JIT compiler generates template code in the browser and evaluates it with eval or new Function, which a script-src without 'unsafe-eval' blocks.

solid answer

~40 s

An Angular template is not JavaScript; something has to turn it into the instructions that create and update the DOM. With **AOT**, the default for Angular CLI builds, that happens at build time: templates become ordinary functions in the bundle, and at runtime nothing is compiled from strings. With **JIT**, the compiler ships to the browser, generates the template code there as text and turns it into functions through `eval`/`new Function`. A CSP whose `script-src` lacks `'unsafe-eval'` blocks exactly that, so a JIT app fails to render; under Trusted Types it also needs the `angular#unsafe-jit` policy. AOT is also Angular's defence against **template injection**, because templates are trusted code and compiling them at runtime invites user data into them.

go deeper

for a junior

Recall that AOT compiles templates during the build and JIT compiles them in the browser, and that AOT is the CLI default.

for a middle

Explain that JIT turns generated source text into functions with eval or new Function, which a CSP blocks without 'unsafe-eval'.

for a senior

Tie the choice to production hardening: AOT keeps 'unsafe-eval' and angular#unsafe-jit out of the policy and removes template injection as a bug class.

for a principal

Treat any runtime template compilation as an architecture decision, and steer data-driven UI toward data plus bindings rather than generated templates.

## Templates must become code somewhere An Angular template such as `<p>{{ user().name }}</p>` is not something the browser understands. Angular's **template compiler** turns it into JavaScript: functions that create the DOM nodes, and functions that update bindings when state changes. The question is **when** and **where** that compilation runs. | Mode | When templates compile | What runs in the browser | | :--- | :--- | :--- | | **AOT** (ahead-of-time) | during the build | pre-generated template functions in the bundle | | **JIT** (just-in-time) | in the browser at startup | the compiler itself, which generates code from strings | The security guide calls AOT the default compiler for Angular CLI applications and says to use it in all production deployments. ## Why JIT needs 'unsafe-eval' A JIT compiler produces **source text** for each template and must turn that text into a callable function. In Angular 22.2 the compiler does it through the global `eval` (or `new Function` where Trusted Types are unavailable). Both are string-to-code APIs, and a Content Security Policy controls them with the `'unsafe-eval'` keyword in `script-src`: 1. The policy is `script-src 'self' 'nonce-…'` with no `'unsafe-eval'`. 2. The JIT compiler tries to evaluate the generated template code. 3. The browser refuses, reports a violation, and the component never renders. Adding `'unsafe-eval'` would make JIT work, but it also re-enables `eval` for any script an attacker manages to influence, which is why strict policies leave it out. ## Why AOT does not need it With AOT the generated functions are part of the bundle, loaded as ordinary script files that `script-src 'self'` or a nonce already allows. At runtime Angular **executes** template functions; it never **creates** them from strings. The guide's minimal policy for a new Angular app therefore contains no `'unsafe-eval'` at all: ```txt default-src 'self'; style-src 'self' 'nonce-randomNonceGoesHere'; script-src 'self' 'nonce-randomNonceGoesHere'; ``` ## Trusted Types and JIT If the policy also enforces **Trusted Types**, the JIT compiler wraps its generated code through a dedicated policy named `angular#unsafe-jit`, and the guide says you must allow that policy whenever the app interacts with the JIT compiler or runs in JIT mode through `platformBrowserDynamic`. The `unsafe` in the name signals that it lets strings become code. ## The second reason: template injection Angular treats **templates as trusted code**: values bound into a template are sanitized, but the template text itself is not. If an app assembled a template string from user input and compiled it at runtime, the attacker would be writing Angular code, with no sanitizer in the way. AOT removes the runtime compiler, so this class of bug, which the guide calls **template injection**, is not reachable in a production build. For UI that must be data-driven, the guide points to building it from data with normal bindings, as its dynamic forms guide does, rather than generating templates. ## Where JIT still shows up - **`platformBrowserDynamic`** from `@angular/platform-browser-dynamic`, the old JIT bootstrap. All of that package's entries are deprecated since Angular 20; `bootstrapApplication` from `@angular/platform-browser` is the current path. - Code that calls the runtime `Compiler` or compiles components on the fly. - Test setups that compile components in the test run; these do not ship to users, so they do not affect the production CSP. ## How to tell which mode an app is in An Angular CLI build compiles AOT unless the project has opted out, so the usual suspects are code paths that bring the compiler back: a legacy bootstrap through `platformBrowserDynamic` that runs in JIT mode, code that compiles components from template strings at runtime, or a dependency that ships uncompiled components. In the browser, the tell is a CSP violation about evaluating a string as JavaScript right at startup, before anything renders. Turning the policy on in development is the cheapest way to find out. ## What to say in the interview - AOT compiles templates at build time; JIT compiles them in the browser. - JIT turns generated source text into functions with `eval`/`new Function`, which needs `'unsafe-eval'` and, under Trusted Types, `angular#unsafe-jit`. - AOT needs neither and also rules out template injection, so production builds should always be AOT.

  • Would adding 'unsafe-eval' to script-src be an acceptable way to keep a JIT build running in production?
    It would make the build run, but it weakens the policy for the whole page: any script an attacker can influence may now evaluate strings as code. It also keeps the runtime compiler in the bundle and leaves template injection reachable. The fix is to build with AOT and drop the JIT bootstrap.
  • Does an AOT build need any Trusted Types policy for its templates?
    It needs the `angular` policy, which Angular uses internally for values it has already sanitized and for trusted template constants, but not `angular#unsafe-jit`. That policy is only for code that is generated and evaluated at runtime by the JIT compiler.

saying these in an interview costs you the question

  • Angular templates are interpreted at runtime, so every app needs 'unsafe-eval'.
  • AOT and JIT differ only in build speed, not in what the browser executes.
  • Adding 'unsafe-eval' to a production CSP is a harmless way to support JIT.
  • Building templates from user strings is safe because Angular sanitizes bindings.
  • platformBrowserDynamic is still the recommended way to bootstrap an app.
open as a page

Under a nonce-based CSP, why do an Angular app's component styles need a nonce, and when do you use CSP_NONCE versus the ngCspNonce attribute?

level: middleimportance: should knowfreq 34%

basics

~20 s

Angular inserts component styles as <style> elements at runtime, which a strict style-src blocks unless they carry the nonce. Use ngCspNonce when the server templates index.html per response, and CSP_NONCE when the nonce arrives at runtime and index.html stays cacheable.

open as a page

You are deploying a strict nonce-based CSP for an Angular banking dashboard; what does Angular itself require in the policy, and which app features would force you to loosen it?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Angular's minimal policy is default-src 'self' plus a per-response nonce in style-src and script-src, handed to Angular via ngCspNonce or CSP_NONCE. JIT compilation, JSONP, bypassed values under Trusted Types and missing nonce plumbing are what push teams to loosen it.

open as a page

When you enforce Trusted Types on an Angular app, which Angular policy names must the trusted-types directive allow, and what breaks if one is missing?

level: seniorimportance: nice to knowfreq 24%

basics

~10 s

The angular policy is always required; angular#unsafe-bypass is needed for bypassSecurityTrust methods, angular#bundler for CLI lazy chunks, angular#unsafe-jit for JIT and angular#unsafe-upgrade for AngularJS hybrids. Without one, that feature's DOM writes fail as violations.

open as a page