Introduction
Modern React applications usually escape rendered values automatically, which removes many common cross-site scripting (XSS) risks. However, a production application can still interact with browser APIs that handle HTML, URLs, scripts, or other potentially dangerous values.
This becomes more important when an application uses:
dangerouslySetInnerHTMLThird-party libraries
Client-side HTML transformations
Browser APIs that accept HTML strings
Content Security Policy (CSP)
Trusted Types
React 19.3 improves the framework's integration with browser Trusted Types. React now supports Trusted Types values for relevant DOM operations, and the React team documents CSP and Trusted Types as part of the security improvements around the React 19.3 release. (react.dev)
Trusted Types are a browser security mechanism designed to prevent DOM-based XSS by requiring dangerous DOM sinks to receive approved trusted values instead of arbitrary strings.
The important distinction is that Trusted Types do not replace React's escaping. They add another security boundary around browser APIs that can interpret strings as executable or unsafe content.
What Are Trusted Types?
Trusted Types are a browser security feature that can restrict certain DOM APIs from accepting ordinary strings.
Without Trusted Types:
element.innerHTML = userInput;
can become dangerous if userInput contains executable markup.
With a Trusted Types policy, the application can instead establish a controlled transformation:
Untrusted Input
|
v
Validation / Sanitization
|
v
Trusted Type
|
v
Dangerous DOM Sink
The browser then enforces the policy.
The goal is to prevent application code from accidentally sending untrusted strings directly to sensitive DOM APIs.
What Is a DOM Sink?
A DOM sink is an API that consumes a value in a way that can create executable or otherwise security-sensitive browser content.
A common example is:
element.innerHTML = value;
Other browser APIs can also be sensitive depending on the value being supplied.
The security problem is:
User Input
|
v
String
|
v
Dangerous DOM API
|
v
XSS
Trusted Types changes the boundary:
User Input
|
v
Sanitizer / Policy
|
v
TrustedHTML
|
v
DOM API
What Is CSP?
Content Security Policy is an HTTP security mechanism that lets a website tell the browser which types of content and execution are allowed.
A CSP header might contain:
Content-Security-Policy: default-src 'self'
CSP can also enable Trusted Types enforcement.
A policy can include directives such as:
Content-Security-Policy:
require-trusted-types-for 'script';
trusted-types my-policy;
The first directive tells the browser to require Trusted Types for supported script-related DOM sinks.
The second restricts which Trusted Types policies can be created.
The exact CSP should be designed around the application's resources and deployment model rather than copied blindly.
Why React Needs Trusted Types
React already protects normal JSX interpolation.
For example:
function Welcome({ name }) {
return <h1>Hello, {name}</h1>;
}
If:
name = "<img src=x onerror=alert(1)>"
React does not interpret the value as HTML.
It treats the value as text.
The risk is higher when developers intentionally bypass normal escaping:
function Content({ html }) {
return (
<div
dangerouslySetInnerHTML={{ __html: html }}
/>
);
}
This API exists for legitimate use cases, but the application is now responsible for ensuring that the HTML is safe.
Trusted Types can add browser-enforced protection around this boundary.
React 19.3 and Trusted Types
React 19.3 improves Trusted Types integration so that Trusted Types values can be passed through relevant React APIs without React unnecessarily converting them back into ordinary strings. The React 19.3 announcement also notes that React no longer stringifies Trusted Types values for relevant DOM operations. (react.dev)
The practical architecture is:
React Component
|
v
Trusted Value
|
v
React DOM
|
v
Browser Trusted Types Enforcement
This is useful when an application has a security policy requiring Trusted Types.
A Basic Trusted Types Policy
The browser API can create a policy:
const policy = trustedTypes.createPolicy("app", {
createHTML(value) {
return DOMPurify.sanitize(value);
}
});
Then:
const trustedHtml = policy.createHTML(untrustedHtml);
The important part is the sanitizer.
Trusted Types do not make arbitrary input safe automatically.
A policy that simply returns the input:
createHTML(value) {
return value;
}
does not provide meaningful sanitization.
The policy should enforce the application's security rules.
Using Trusted HTML With React
A component can receive a trusted value:
function ArticleContent({ content }) {
return (
<div
dangerouslySetInnerHTML={{
__html: content
}}
/>
);
}
The security architecture becomes:
External HTML
|
v
Sanitization
|
v
TrustedHTML
|
v
React
|
v
DOM
The type itself is not the sanitizer.
The policy that creates the trusted value is where the security decision is made.
Do Not Sanitize in the Wrong Place
A common mistake is to sanitize some data at the point where it enters the application and then assume it will always remain safe.
Data can change later.
For example:
Database
|
v
API
|
v
React
|
v
DOM
If the application stores HTML and later displays it, the rendering boundary still needs to be considered.
The correct architecture depends on what the data represents.
For user-generated HTML, establish a clear rule:
Input
|
v
Validation
|
v
Sanitization
|
v
Trusted Representation
|
v
Rendering
Do not rely on an undocumented assumption that "this field is already safe."
CSP and Trusted Types Work Together
Trusted Types are most useful when enforced through CSP.
A simplified policy might look like:
Content-Security-Policy:
require-trusted-types-for 'script';
trusted-types app;
The browser then restricts supported dangerous sinks.
Conceptually:
React Application
|
v
DOM Sink
|
v
Trusted Types Check
|
+---- TrustedHTML ---> Allowed
|
+---- String --------> Blocked
This turns a security rule into browser-enforced behavior instead of relying entirely on developer discipline.
Start With Report-Only Mode
Changing CSP enforcement on an existing application can expose compatibility problems.
A safer rollout begins with:
Content-Security-Policy-Report-Only:
require-trusted-types-for 'script';
trusted-types app;
Report-only mode allows teams to identify violations before enforcing the policy.
The deployment process can be:
Report Only
|
v
Collect Violations
|
v
Fix Application / Dependencies
|
v
Test Again
|
v
Enforce CSP
This is especially useful for large applications with third-party dependencies.
Third-Party Libraries Are a Common Problem
Your application may be safe while a dependency still writes directly to a sensitive DOM sink.
For example:
element.innerHTML = someString;
If Trusted Types enforcement is enabled, the dependency may fail.
This is one reason CSP rollout should be treated as an application-wide change.
Audit:
UI libraries
Markdown renderers
Rich-text editors
Charting libraries
Analytics packages
Legacy components
Browser extensions integrated into the application
Do not assume that every dependency follows the same security model as React.
Trusted Types Do Not Fix Server-Side XSS
Trusted Types are primarily a browser-side DOM protection mechanism.
They do not replace:
Server-side input validation
Output encoding
Authentication
Authorization
CSRF protection
Secure cookie configuration
SQL injection prevention
Secure HTTP headers
Dependency security
Think of Trusted Types as one layer:
Secure Architecture
|
+---- Authentication
+---- Authorization
+---- Input Validation
+---- Output Encoding
+---- CSP
+---- Trusted Types
+---- Dependency Security
No single browser security feature solves every injection problem.
What About URLs?
Trusted Types can also be relevant to APIs that consume trusted script URLs or other security-sensitive values, depending on browser support and the specific DOM sink.
The important rule is not to treat every URL as safe simply because it is a URL.
For example:
window.location = userProvidedUrl;
requires URL validation for the application's expected destinations.
Trusted Types are not a substitute for validating whether a URL should be allowed.
Use Narrow Policies
Avoid creating a large number of policies.
For example:
app-html
app-script
legacy-widget
third-party-widget
temporary-policy
can become difficult to audit.
Prefer a small number of clearly owned policies:
Application
|
+---- Trusted Types Policy
|
+---- Sanitization Rules
Every policy should have an explicit reason for existing.
Keep Sanitization Logic Centralized
Instead of allowing every component to implement its own sanitizer:
sanitizeA(html)
sanitizeB(html)
sanitizeC(html)
centralize the security boundary:
function createSafeHtml(value) {
return appPolicy.createHTML(value);
}
Then components consume the trusted result.
This makes security behavior easier to audit.
Testing Trusted Types
A useful test suite should verify both allowed and rejected paths.
Safe Content
Trusted HTML
|
v
React
|
v
Rendered
Untrusted Content
Raw string
|
v
Protected DOM sink
|
v
Blocked
Test cases should include:
Normal HTML
Script elements
Event-handler attributes
Dangerous URLs
SVG content
Malformed HTML
Third-party generated HTML
Markdown-generated HTML
Rich-text editor output
The goal is to verify that the security boundary works when developers accidentally bypass the intended flow.
Common Mistakes
Assuming React Automatically Makes All HTML Safe
Normal JSX escaping is different from explicitly inserting HTML.
Creating a Trusted Types Policy That Returns Input Unchanged
That defeats the purpose of the security boundary.
Enabling Enforcement Without Auditing Dependencies
Third-party libraries may use incompatible DOM APIs.
Treating TrustedHTML as Sanitization
The policy creates the trusted value; it must still apply appropriate sanitization.
Using Trusted Types Instead of Input Validation
Trusted Types do not replace server-side validation or business rules.
Enabling CSP Directly in Production
Use report-only testing first for an existing application.
Creating Policies Everywhere
Centralize security-sensitive transformations.
Best Practices
Keep normal JSX rendering as the default.
Minimize uses of
dangerouslySetInnerHTML.Centralize HTML sanitization.
Use a well-maintained sanitizer for untrusted HTML.
Create narrowly scoped Trusted Types policies.
Use CSP to enforce the Trusted Types requirement.
Start with report-only mode for existing applications.
Audit third-party dependencies before enforcement.
Test dangerous DOM sinks explicitly.
Treat Trusted Types as one layer of a broader security architecture.
Review URL-handling code separately.
Monitor CSP violations after deployment.
Trusted Types vs React Escaping
Security mechanism | Protects against | Main limitation |
|---|---|---|
React JSX escaping | Many normal XSS injection paths | Does not cover intentional raw HTML insertion |
HTML sanitizer | Unsafe HTML content | Must be configured and maintained correctly |
Trusted Types | Unsafe strings reaching supported DOM sinks | Does not determine whether application data is semantically safe |
CSP | Restricts browser resource/script behavior | Incorrect policies can break legitimate application functionality |
Server-side validation | Invalid or malicious input | Cannot replace browser-side DOM protections |
These mechanisms work best together rather than as replacements for each other.
Production Rollout
A practical rollout can follow these stages:
1. Inventory dangerous DOM sinks
|
v
2. Identify raw HTML usage
|
v
3. Audit third-party dependencies
|
v
4. Create centralized Trusted Types policy
|
v
5. Enable CSP Report-Only
|
v
6. Fix policy violations
|
v
7. Run integration tests
|
v
8. Enable CSP enforcement
|
v
9. Monitor violations and application errors
This approach avoids turning a security improvement into an unexpected production outage.
Production Checklist
[ ] React version supports the required Trusted Types behavior
[ ] Raw HTML usage has been inventoried
[ ] dangerouslySetInnerHTML usage has been reviewed
[ ] HTML sanitization is centralized
[ ] Trusted Types policy is narrowly scoped
[ ] Policy does not blindly trust input
[ ] Third-party DOM sinks have been audited
[ ] CSP Report-Only has been tested
[ ] Violations have been reviewed
[ ] Integration tests pass
[ ] CSP enforcement has been tested
[ ] URL handling has been reviewed separately
[ ] Security headers are monitored
[ ] Trusted Types policy ownership is documented
Summary
React 19.3 improves the framework's integration with browser Trusted Types, making it easier for applications to combine React rendering with browser-enforced DOM security policies. (react.dev)
The security model can be summarized as:
Untrusted Data
|
v
Validation / Sanitization
|
v
Trusted Type
|
v
React
|
v
Browser DOM
|
v
CSP Enforcement
The biggest benefit is that Trusted Types can turn a security guideline into a browser-enforced rule for supported DOM sinks.
However, the feature does not make unsafe data automatically safe. A poorly designed Trusted Types policy can still trust dangerous content, and Trusted Types do not replace server-side validation, authorization, output encoding, or other security controls.
For an existing React application, the safest approach is incremental: audit raw HTML and DOM sinks, centralize sanitization, test dependencies, use CSP Report-Only first, and enable enforcement only after violations have been addressed.

Join the conversation! Your thoughts help the community grow.