Untrusted text must not become executable markup, but preserving valid Unicode is also important. A user may submit Arabic prose, emoji ZWJ sequences, combining marks or stylized mathematical letters. A safe renderer separates HTML output encoding, directionality isolation, normalization and identifier-risk analysis rather than trying to solve every concern by stripping special characters.
Output encoding prevents markup injection, not lookalikes
When inserting untrusted text in a web page, prefer DOM APIs such as textContent that create text nodes rather than parsing strings as HTML. Escaping in a server-rendered template must be appropriate to the current output context.
A string containing literal <script> is safe as text but dangerous if wrongly injected as live markup. Unicode confusables are a separate display problem: correct HTML escaping does not stop a Cyrillic letter from resembling a Latin letter.
HTML, attribute, URL and JavaScript contexts differ
Text-node encoding, quoted attribute encoding, URL validation and JavaScript literal escaping obey different grammars. A generic function named sanitizeText cannot safely implement all of them without context.
Avoid constructing event-handler attributes or JavaScript code using untrusted strings. Use DOM setters for text, platform URL parsers and a strict allowed-protocol policy for links. When rich HTML must be accepted, process it through a maintained sanitizer with an explicit element/attribute allowlist.
Use bidi isolation for embedded names and identifiers
An Arabic label with an inserted Latin invoice number can reorder punctuation in a surprising way. HTML <bdi> isolates a fragment with potentially unknown direction; suitable dir settings help establish a paragraph base direction.
If possible, use markup rather than inserting invisible format characters into the stored user's name. Preserve logical text order for copy/paste. Never 'fix' a bidi display by reversing UTF-16 code units.
Preserve invisible characters with contextual inspection
ZWJ, ZWNJ, variation selectors and directional isolates can be valid in emoji and multilingual text. These code points may be invisible to readers, but a machine-identifier inspector should reveal them using U+XXXX labels and Unicode names.
Diagnostic markers should not become part of the original saved string. Offer read-only code-point inspection, and keep different actions for copying original content versus escaped diagnostic text.
Normalize only under a declared data contract
NFC can support canonical-equivalent comparisons, and NFKC can collapse compatibility characters. Neither operation substitutes for HTML escaping or UTS #39 confusable detection. Normalization can change the exact string users expect to retrieve later.
For prose, preserve submitted source unless a product promises normalization. For account handles, validate and canonicalize under a versioned identifier profile, store original display spelling separately, and handle collisions explicitly.
Reveal verified identity separately from display styling
A display name styled with fancy Unicode letters can be visually prominent, but it is not a verified legal identity, domain origin or package publisher name. Security-sensitive components should expose durable verified identifiers and origin data in their own UI elements.
Do not rely solely on badges made from Unicode symbols. Provide accessible labels and, when appropriate, verified account metadata. A text conversion tool may help illustrate how characters differ from CSS style; it is not an identity validation service.
Keep cross-script confusable checks field-specific
UTS #39 provides comparison mechanisms, but false positives are expected. A multilingual comment field should retain legitimate Greek, Arabic or Cyrillic text; a high-value handle registration may warrant stronger restrictions against protected-name collisions.
Where a warning appears, explain what is potentially confusing and which rule applied, without falsely claiming malicious intent. Hard rejections should be supported by a documented account or protocol policy rather than by vague suspicion of Unicode characters.
Make content security and visual security testable
Build a fixture with literal HTML markup, a mixed-direction sentence, a combining-mark sequence, an emoji ZWJ and a lookalike account name. Verify the markup displays as text without execution, directionality is intelligible and scalar sequences survive copy/paste.
Then independently test that the inspector identifies controls, any risky identifier produces an appropriate warning, and assistive technologies expose useful labels. A screenshot alone cannot establish DOM safety or byte-level identity.
Defend against truncation and ambiguous clipping
Ellipsized names can hide a differing suffix, domain or critical code point. A card displaying the first ten painted glyphs may not indicate which Unicode scalars are omitted, especially with combining marks or bidi runs.
Use full-text reveal mechanisms for high-trust choices, avoid placing the only verified identifier inside a clipped area and retain logical reading order. Measure grapheme boundaries when truncating human-facing text, not arbitrary bytes or UTF-16 code units.
Avoid destructive blanket sanitization
Replacing every non-ASCII code point with a question mark prevents many legitimate languages, destroys emoji and can erase data needed for auditing. The appropriate safeguards are contextual output encoding, rich-HTML sanitization where needed, direction isolation and narrowly defined identifier policy.
An application that maintains both trustworthy UI and Unicode fidelity will surface uncertainty explicitly: 'unverified display name', 'unsupported presentation' or 'confusable identifier' are distinct states, and each requires a different response.
Reproducible example and scope
This example isolates one specific aspect of text representation, validation or accessible content. It is not a full production security validator or a comprehensive accessibility test: field-specific policy, Unicode data releases, browser behavior and assistive-technology feedback require independent verification.
// Correct: untrusted user text is inserted as text, not HTML.
const label = document.createElement('span');
label.textContent = userSuppliedName; // provided by application
const bdi = document.createElement('bdi');
bdi.append(label);
document.querySelector('#profile-name').append(bdi);
// For URLs: parse and validate scheme/host separately before linking.
// Do not assign userSuppliedName directly to innerHTML.Capture the source code points, context, any intentional transformation and observed outcome. Exact Unicode identity, comparison-key equivalence and user comprehension are separate measurements. Preserve the original value when generating derived search, display or inspection forms.
Evidence and interoperability comparison
| Case | Specimen | Interpretation |
|---|---|---|
| DOM text | textContent | does not parse markup |
| Rich HTML | sanitizer allowlist | different trust boundary than plain text |
| Bidi fragment | <bdi> | contains directional context |
| Lookalike identifier | UTS #39 check | warn/review under field policy |
| Stylized Unicode | NFKC-sensitive | exact source should not be silently changed |
| Truncation | grapheme-aware ellipsis | preserve identity cues on demand |
Do not treat a successful string normalization or a valid HTML structure as evidence that every possible attack has been prevented or that every screen reader will pronounce the result correctly. Match each claim to an applicable specification and observable test.
Testing protocol and limitations
- DOM text. Build a controlled test of
textContent, retaining the actual Unicode scalar sequence and relevant browser, screen-reader or protocol version. Expected interpretation: does not parse markup. Document both correct behavior and plausible false positives. - Rich HTML. Build a controlled test of
sanitizer allowlist, retaining the actual Unicode scalar sequence and relevant browser, screen-reader or protocol version. Expected interpretation: different trust boundary than plain text. Document both correct behavior and plausible false positives. - Bidi fragment. Build a controlled test of
<bdi>, retaining the actual Unicode scalar sequence and relevant browser, screen-reader or protocol version. Expected interpretation: contains directional context. Document both correct behavior and plausible false positives. - Lookalike identifier. Build a controlled test of
UTS #39 check, retaining the actual Unicode scalar sequence and relevant browser, screen-reader or protocol version. Expected interpretation: warn/review under field policy. Document both correct behavior and plausible false positives. - Stylized Unicode. Build a controlled test of
NFKC-sensitive, retaining the actual Unicode scalar sequence and relevant browser, screen-reader or protocol version. Expected interpretation: exact source should not be silently changed. Document both correct behavior and plausible false positives. - Truncation. Build a controlled test of
grapheme-aware ellipsis, retaining the actual Unicode scalar sequence and relevant browser, screen-reader or protocol version. Expected interpretation: preserve identity cues on demand. Document both correct behavior and plausible false positives.
For system-level verification, add adversarial input and legitimate multilingual exceptions. Test both the algorithmic result and the human decision it supports. A high-quality report includes exact Unicode scalars, environment versions, expectations, policy choice and clear error classification.
Interpreting unintended changes
When a result differs, determine whether the change arises during byte decoding, normalization, security classification, rendering, assistive technology or the clipboard. A fix targeting the wrong layer can silently destroy valid language text or leave the original ambiguity unresolved.
Primary standards and implementation documentation
Use the following primary references to verify normative requirements and data-version implications. A Unicode Technical Standard, core Unicode conformance, browser accessibility behavior and application policy are distinct mechanisms with different obligations.
- WHATWG HTML Standard — primary specification or accessibility reference; verify its applicable version and distinguish normative language from implementation-specific recommendations.
- Unicode Bidirectional Algorithm UAX #9 — primary specification or accessibility reference; verify its applicable version and distinguish normative language from implementation-specific recommendations.
- UTS #39 Unicode Security Mechanisms — primary specification or accessibility reference; verify its applicable version and distinguish normative language from implementation-specific recommendations.
- UAX #29 Text Segmentation — primary specification or accessibility reference; verify its applicable version and distinguish normative language from implementation-specific recommendations.
- W3C WAI WCAG 2.2 — primary specification or accessibility reference; verify its applicable version and distinguish normative language from implementation-specific recommendations.
- MDN Node.textContent — primary specification or accessibility reference; verify its applicable version and distinguish normative language from implementation-specific recommendations.
Related published research
The Identifiers, Confusables & Security virtual silo connects these technical notes to the broader Unicode interoperability research library. Links below point to other published documents with adjacent concerns.
- invisible character spoofing
- unicode confusables lookalikes
- unicode copy paste round trips
- right to left text web interfaces
Keep user-facing content independently intelligible, and maintain an explicit distinction between decorative output, its encoded characters and any alternate form supplied for accessibility or security review.