Modern React applications usually provide strong protection against common Cross-Site Scripting (XSS) attacks because React escapes text inserted into the DOM by default.
For example:
function UserProfile({ name }) {
return <h2>{name}</h2>;
}
If name contains HTML, React treats it as text rather than executing it as markup.
The security picture changes when an application intentionally works with raw HTML, browser DOM APIs, third-party libraries, or APIs such as dangerouslySetInnerHTML.
This is where Trusted Types can provide another layer of protection.
React 19.3 improves its integration with Trusted Types, allowing applications to use browser-enforced Trusted Types policies more effectively when React writes content to sensitive DOM sinks.
This article explains what Trusted Types are, how DOM injection happens, how React's integration works, and how developers can use the feature without weakening existing application security.
What Is DOM Injection?
DOM injection occurs when untrusted data reaches a browser API that interprets the data as HTML, JavaScript, or another executable format.
A simple example is:
element.innerHTML = userInput;
If userInput contains HTML that the application did not intend to execute, the browser may interpret it as markup.
For example, code such as:
const content = "<img src=x onerror=alert('XSS')>";
element.innerHTML = content;
creates an unsafe DOM injection scenario.
React normally avoids this problem when rendering ordinary strings:
function Message({ message }) {
return <div>{message}</div>;
}
React escapes the value instead of treating it as HTML.
The risk increases when developers intentionally bypass normal escaping.
What Is Trusted Types?
Trusted Types is a browser security mechanism that helps prevent dangerous strings from being passed directly into sensitive DOM APIs.
Instead of allowing an application to assign arbitrary strings to a sink such as:
element.innerHTML = value;
a Content Security Policy can require a trusted value.
Conceptually, the browser distinguishes between:
Untrusted string
and:
TrustedHTML
An application creates a Trusted Types policy that defines how a value becomes trusted.
For example:
const policy = trustedTypes.createPolicy("app", {
createHTML(value) {
return sanitize(value);
}
});
The important part is that sanitize() must actually make the HTML safe.
Trusted Types do not magically sanitize arbitrary HTML.
They establish a controlled boundary where the application decides what is allowed to reach a dangerous DOM sink.
Why Trusted Types Matter in React Applications
React already escapes normal text output.
So why would a React application need Trusted Types?
Because real applications frequently contain code outside normal JSX escaping.
Examples include:
dangerouslySetInnerHTMLThird-party editors
HTML rendering libraries
DOM manipulation
Legacy JavaScript
Browser APIs
Content management systems
Markdown or rich-text rendering
A security problem can occur when one of these paths bypasses React's normal escaping behavior.
Trusted Types can provide an additional browser-enforced layer.
The goal is defense in depth:
User Input
↓
Validation / Sanitization
↓
Trusted Types Policy
↓
DOM Sink
Rather than:
User Input
↓
DOM Sink
React's Normal HTML Escaping
Consider:
function Comment({ text }) {
return <p>{text}</p>;
}
If the user submits:
<script>alert("test")</script>
React renders the value as text.
The browser does not execute it as a script.
This is one reason developers should prefer normal JSX whenever possible.
For example:
<p>{comment.text}</p>
is generally safer than:
<p dangerouslySetInnerHTML={{ __html: comment.text }} />
The second example explicitly asks React to interpret the value as HTML.
The Risk of dangerouslySetInnerHTML
There are legitimate reasons to render HTML.
For example, a CMS may return formatted content:
<h2>Introduction</h2>
<p>This is an article.</p>
An application might render it with:
function Article({ html }) {
return (
<article
dangerouslySetInnerHTML={{
__html: html
}}
/>
);
}
The problem is not the API itself.
The problem is trusting the input.
If html contains attacker-controlled content, the application has created an injection opportunity.
A safer architecture is:
CMS content
↓
Sanitization
↓
Trusted representation
↓
React
↓
DOM
Trusted Types can strengthen this boundary by allowing the browser to reject inappropriate values at protected DOM sinks.
Enabling Trusted Types with Content Security Policy
Trusted Types are enforced through Content Security Policy.
A policy can require trusted HTML for relevant DOM sinks.
Conceptually, a CSP can contain:
require-trusted-types-for 'script'
An application can also specify which Trusted Types policies are permitted.
The exact CSP configuration should be designed according to the application's framework, deployment model, third-party scripts, and existing security policy.
Do not copy a security header into production without testing the entire application.
A restrictive policy can expose previously hidden DOM-writing behavior in:
Analytics scripts
Authentication widgets
Rich-text editors
Third-party components
Legacy application code
Creating a Trusted Types Policy
A policy should have a narrow purpose.
For example:
const policy = trustedTypes.createPolicy("app-html", {
createHTML(input) {
return sanitizeHtml(input);
}
});
The sanitization function is the critical part.
A policy like this is dangerous:
const policy = trustedTypes.createPolicy("unsafe", {
createHTML(input) {
return input;
}
});
This effectively declares arbitrary input trustworthy.
It defeats the security objective.
Trusted Types should not become a mechanism for simply suppressing browser security errors.
React and TrustedHTML
Modern React can work with Trusted Types values rather than requiring developers to convert every trusted value back into an ordinary string.
A conceptual example is:
function Article({ trustedContent }) {
return (
<article
dangerouslySetInnerHTML={{
__html: trustedContent
}}
/>
);
}
The important security boundary remains the creation of trustedContent.
For example:
const trustedContent = policy.createHTML(
sanitizedHtml
);
The application should not accept arbitrary HTML and immediately mark it as trusted.
React's Trusted Types support is intended to work with browser security mechanisms without requiring applications to unnecessarily weaken those protections.
Trusted Types Are Not an HTML Sanitizer
This is one of the most important points.
Trusted Types answer:
"Is this value allowed to reach a protected DOM sink?"
They do not inherently answer:
"Is this HTML safe?"
That is the responsibility of the Trusted Types policy.
Consider:
const policy = trustedTypes.createPolicy("app", {
createHTML(value) {
return value;
}
});
This creates a TrustedHTML value but does not make unsafe HTML safe.
A proper implementation needs a trusted sanitization step:
const policy = trustedTypes.createPolicy("app", {
createHTML(value) {
return sanitizeHtml(value);
}
});
The sanitizer itself must be configured correctly for the application's content model.
Trusted Types vs Traditional Input Validation
Trusted Types and input validation solve different problems.
Security technique | Primary purpose |
|---|---|
Input validation | Reject invalid input |
Output encoding | Prevent data from being interpreted as executable content |
HTML sanitization | Remove unsafe HTML |
CSP | Restrict browser execution behavior |
Trusted Types | Control values reaching sensitive DOM sinks |
React escaping | Safely render ordinary text |
A strong application can use several of these controls together.
Trusted Types should not be treated as a replacement for authentication, authorization, input validation, sanitization, or secure coding practices.
Handling Third-Party Libraries
Third-party libraries are a common reason to introduce Trusted Types.
Suppose a library contains:
element.innerHTML = generatedHtml;
When Trusted Types enforcement is enabled, that code may fail if the value does not meet the browser's Trusted Types requirements.
This can initially look like a React problem, but the actual issue may be inside a dependency.
A practical troubleshooting process is:
Identify the DOM sink producing the error.
Determine which library or component is calling it.
Check whether the library supports Trusted Types.
Upgrade to a compatible version if available.
Replace the library if it cannot safely operate under the application's security policy.
Avoid creating a broad policy simply to make the library work.
This approach keeps the security boundary intact.
Common Mistakes
Trusting User Input Directly
Avoid:
policy.createHTML(userInput);
unless userInput has passed through an appropriate sanitization process.
Creating a Policy That Returns the Input
Avoid:
createHTML(value) {
return value;
}
This provides the appearance of Trusted Types protection without actually creating a meaningful trust boundary.
Assuming React Escapes dangerouslySetInnerHTML
It does not.
This API intentionally bypasses normal text escaping.
<div
dangerouslySetInnerHTML={{
__html: html
}}
/>
The input must therefore be controlled and appropriately sanitized.
Ignoring Non-React DOM Code
A React application can still contain:
element.innerHTML = value;
or third-party code that performs equivalent operations.
Security reviews should cover the entire browser application, not only JSX.
Allowing Too Many Trusted Types Policies
A large collection of policies can make security difficult to understand.
Prefer a small number of clearly named policies with well-defined responsibilities.
Troubleshooting Trusted Types Errors
A common browser error indicates that a value cannot be assigned to a protected DOM sink.
The first step is to identify the sink.
Look for operations such as:
innerHTML
outerHTML
insertAdjacentHTML
and related APIs.
Then determine where the value originated.
For example:
CMS content
↓
Markdown renderer
↓
HTML string
↓
innerHTML
The security review should focus on the transition between those stages.
If the content is legitimate HTML, introduce a controlled sanitization and Trusted Types policy.
If the content does not need to be HTML, the better solution may be to render it as normal text.
Testing a Trusted Types Policy
Security controls should be tested before enforcement is enabled broadly.
Start by identifying existing DOM sinks.
Then test important application flows:
Login
Registration
Comments
Rich-text editing
CMS content
Search
Error pages
Notifications
Third-party integrations
Client-side navigation
Pay particular attention to functionality that dynamically creates HTML.
A Content Security Policy can also be introduced in a reporting or controlled rollout strategy before strict enforcement, depending on the application's security deployment process.
Production Best Practices
Prefer Normal JSX
Whenever possible:
<p>{content}</p>
is preferable to:
<p dangerouslySetInnerHTML={{ __html: content }} />
Sanitize HTML at a Defined Boundary
Do not scatter sanitization logic throughout components.
Create a clear security boundary for HTML content.
Keep Trusted Types Policies Small
Each policy should have a clear reason to exist.
Audit DOM Sinks
Search application code and dependencies for dangerous DOM APIs.
Test Third-Party Components
A strict Trusted Types policy can expose unsafe assumptions in dependencies.
Combine Security Controls
Use Trusted Types alongside appropriate:
Content Security Policy
Output encoding
Input validation
HTML sanitization
Authentication
Authorization
Dependency management
No single browser security feature eliminates every injection vulnerability.
Advantages and Disadvantages
Advantages
Adds browser-enforced protection around sensitive DOM sinks.
Complements React's existing escaping behavior.
Makes trusted HTML creation more explicit.
Can reduce the impact of DOM-based XSS vulnerabilities.
Helps identify unsafe DOM manipulation in application dependencies.
Provides a stronger security boundary for applications that must process HTML.
Disadvantages
Requires additional security configuration.
Existing third-party libraries may not initially be compatible.
Poorly designed policies can undermine the security benefit.
Does not automatically sanitize HTML.
Can require significant testing in large applications.
Developers need to understand both React rendering and browser security policies.
When Should You Use Trusted Types?
Trusted Types are particularly valuable for applications that:
Render user-generated or CMS-provided HTML
Use
dangerouslySetInnerHTMLDepend heavily on dynamic DOM manipulation
Integrate multiple third-party JavaScript libraries
Have a strong Content Security Policy
Need additional protection against DOM-based XSS
For a simple React application that only renders ordinary strings through JSX, the immediate need may be lower because React already escapes those values.
However, as an application grows, more DOM-writing paths often appear. Trusted Types can then provide a useful additional security layer.
Conclusion
React's default escaping provides an important defense against injection attacks, but real applications often contain situations where developers intentionally work with HTML or browser DOM APIs.
Trusted Types provide a browser-enforced boundary around those sensitive operations.
The most important thing to remember is that Trusted Types are not a replacement for sanitization. A Trusted Types policy should only create trusted values after the application has established that the content is safe for its intended context.
For production React applications, a strong approach is to prefer ordinary JSX text rendering, isolate unavoidable HTML rendering, sanitize content at a defined boundary, keep Trusted Types policies narrow, and test third-party dependencies before enforcing strict policies.
Used this way, Trusted Types complement React's existing security model rather than replacing it, giving applications another layer of protection against DOM injection.

Join the conversation! Your thoughts help the community grow.