React / Performance and trustworthy boundaries / Lesson 17
Security and trusted boundaries
Use React’s text rendering safely while keeping authorization, URL policy, raw HTML, and dependency trust under explicit control.
On this page
Lesson goals
- Start with what an input can control
- Verify ordinary strings remain text
- Protect the real operation and supply chain
Start with what an input can control
List untrusted inputs such as form text, route parameters, API responses, rich content, and dependency updates. Trace each to its output or capability: text, URL, raw HTML, storage, or a server operation. React’s ordinary string rendering prevents that string from being interpreted as markup, but this useful default does not validate all downstream contexts. A text value placed into a URL prop requires a URL policy, and a field sent to a server action requires server validation and authorization. Avoid a universal sanitize function whose meaning is unclear across these different boundaries. Each context needs its own narrow contract.
Keep formatted content structured where possible
Prefer components for known formatting instead of concatenating HTML strings. dangerouslySetInnerHTML bypasses normal text rendering and requires a trustworthy sanitization and content policy suitable for the source and deployment. A regular expression is not a general HTML sanitizer. Content Security Policy can provide defense in depth but does not replace correct handling or authorization. Do not copy raw API content into the escape hatch merely because it was returned by your own endpoint; the endpoint may be carrying user-authored content. Keep the origin, transformations, and permitted markup contract reviewable, including how updates to the sanitizer or parser are maintained.
Verify ordinary strings remain text
The first example displays a string containing harmless markup-looking characters. The visible result includes the angle brackets, and the browser should not contain a strong element created from that string. This is an isolated demonstration of text rendering, not a comprehensive security test. Keeping content structured also helps accessibility because actual headings, labels, and links can be chosen deliberately rather than accepting arbitrary markup. Do not suppress meaningful validation errors or expose sensitive records in console logs while debugging. Error feedback should give the user a correction path without disclosing tokens, private queries, or internal implementation details unnecessarily.
const text = "<strong>draft</strong>";
export default function App() { return <main><h1>Text boundary</h1><p>{text}</p></main>; }
Visible paragraph: <strong>draft</strong>. No strong element is created from the string.
The string is a text child. Its bracketed content is visible rather than becoming a strong element.
Constrain navigation destinations deliberately
The second example accepts only HTTPS URLs on one synthetic approved origin before rendering a link. It uses the URL parser rather than a prefix check that can confuse host boundaries. This policy suits the fixture, not every application: relative links, allowed external destinations, redirects, and download behavior require a documented product policy. A safe browser link policy does not establish SSRF protection for a server that fetches a URL, where DNS resolution, internal addresses, redirects, and capabilities introduce additional concerns. Never assume React validates those semantics merely because a URL is passed through a JSX expression.
export function approvedHref(value) { try { const url = new URL(value); return url.protocol === "https:" && url.origin === "https://docs.example.test" ? url.href : null; } catch { return null; } }
export default function App() { const href = approvedHref("https://docs.example.test/guide"); return <main><h1>Approved destination</h1>{href ? <a href={href}>Read guide</a> : <p>Destination unavailable</p>}</main>; }
Read guide links to https://docs.example.test/guide.
The fixture only computes and renders a link; it makes no request. The exact parsed origin and protocol must satisfy its explicit policy.
Protect the real operation and supply chain
Client UI permissions are guidance, not enforcement. Authenticate and authorize the resource operation on the trusted server, validate inputs, and design CSRF and session behavior according to the chosen framework and deployment. Keep secrets out of delivered bundles and serialized props. Review dependency identity, lockfile changes, supported versions, and official advisories; installing a package can execute code and a clean audit report is not proof of safety. Test negative cases only in authorized isolated fixtures. A secure component API should make dangerous choices explicit and avoid spreading raw HTML or unrestricted URL capabilities through otherwise ordinary presentation props.
Common errors and fixes
Raw content becomes executable markup
- Why it happens
- An untrusted value entered the raw HTML escape hatch.
- How to fix it
- Use text or a reviewed content sanitization boundary.
- Prevent it next time
- Keep raw HTML capabilities explicit and limited.
A hidden admin button is treated as access control
- Why it happens
- Only client presentation checks exist.
- How to fix it
- Authorize the server operation independently.
- Prevent it next time
- Test direct resource access with isolated roles.
A URL prefix check accepts the wrong host
- Why it happens
- String prefixes were mistaken for parsed origin identity.
- How to fix it
- Parse the URL and enforce an explicit policy.
- Prevent it next time
- Include confusing host and protocol cases.
Check your understanding
1. Does rendering a user’s text safely prove that a server action receiving it is safe?
Show worked solution
No. Text rendering addresses a browser markup context. The server must independently validate type and domain constraints, authenticate and authorize the operation, and control its database or other capabilities. One boundary’s guarantee cannot be transferred to another context without analysis.
2. Why avoid a single unrestricted html prop in a broadly reused card component?
Show worked solution
It distributes an escape hatch to many callers and makes the content trust contract easy to overlook. Prefer structured children for normal content and isolate any justified rich-HTML renderer behind a clearly named, reviewed API with an enforced sanitization policy.
Key takeaways
- React’s text default is useful but context-specific.
- Authorize at the real server boundary.
- Make raw HTML and URL capabilities explicit.