Security anti-pattern for Unicode-related vulnerabilities (CWE-176). Use when generating or reviewing code that handles usernames, displays text, validates input, or compares strings. Detects confusable characters, normalization issues, and bidirectional text attacks.
Bidirectional Text Overrides: Control characters reordering display (obfuscating exe.pdf as fdp.exe).
The Anti-Pattern
The anti-pattern is processing Unicode strings without normalization, confusable detection, or control character stripping.
BAD Code Example
# VULNERABLE: Comparing strings without normalization or confusable detection.
def authenticate_user(provided_username, password):
# This example assumes `fetch_user_from_db` expects the exact string from the DB.
stored_user = fetch_user_from_db(provided_username)
if stored_user and stored_user.password == hash_password(password):
# The 'admin' account exists.
# An attacker registers an account with username "аdmin" (Cyrillic 'a').
# The database stores "аdmin".
# When an attacker tries to log in as "аdmin", `provided_username` is "аdmin".
# `fetch_user_from_db` finds the attacker's "аdmin" user.
# But if the application internally processes "аdmin" to "admin"
# in some other place for a check like `if username == "admin"`,
# then "аdmin" might bypass this check.
# Or, more directly, if `fetch_user_from_db` is case/normalization insensitive:
# Attacker registers "Admin" (Latin A).
# Legitimate user is "admin" (Latin a).
# Both may resolve to the same internal user, or one user can spoof another.
return True
return False
# Another example: Allowing confusable characters for domain names.
# Attacker registers "pаypal.com" (with Cyrillic 'a')
# This looks identical to "paypal.com" (with Latin 'a'), enabling phishing.
GOOD Code Example
# SECURE: Normalize, filter, and compare Unicode strings consistently.
import unicodedata
import re
def normalize_and_sanitize_username(username):
# 1. Normalize to canonical form (NFC) for consistent comparison.
# NFC replaces combining characters with precomposed equivalents.
normalized = unicodedata.normalize('NFC', username)
# 2. Strip zero-width and bidirectional control characters.
# Prevents display manipulation and hidden content.
sanitized = re.sub(r'[\u200B-\u200F\u202A-\u202E\u2066-\u2069]', '', normalized)
# 3. Apply confusable detection (recommended for critical identifiers).
# Convert to "skeleton" form or use confusables database.
# (Implementation depends on specific libraries/algorithms).
# 4. Enforce allowlist of permitted characters.
# For usernames, restrict to ASCII alphanumeric and limited symbols.
if not re.fullmatch(r'^[a-zA-Z0-9_.-]+$', sanitized):
raise ValueError("Username contains disallowed characters.")
return sanitized
def authenticate_user_secure(provided_username, password):
# All usernames should be normalized and sanitized consistently before storage and comparison.
sanitized_username = normalize_and_sanitize_username(provided_username)
stored_user = fetch_user_from_db(sanitized_username)
if stored_user and stored_user.password == hash_password(password):
return True
return False
# When displaying usernames or domain names, consider using Punycode for internationalized domain names (IDNs)
# to make spoofing more obvious to users.
Detection
Review string comparisons: Look for any comparisons of user-controlled strings, especially for authentication, authorization, or access control decisions.
Check input processing: See how input strings are handled from reception to storage and display. Are normalization steps applied consistently?
Test with confusable characters: Try registering usernames or domains that use homoglyphs (e.g., Cyrillic 'a' instead of Latin 'a') for common reserved names (admin, root) or well-known brands (paypal, apple).
Test with zero-width characters: Insert zero-width characters (e.g., \u200B) into inputs to see if they bypass length checks or string comparisons.
Prevention
Normalize all Unicode input: Convert all strings to NFC (Normalization Form C) before validation, storage, or comparison.
Strip dangerous control characters: Remove zero-width spaces (\u200B), bidirectional overrides (\u202E), and non-printing characters.
Implement confusable detection: For critical identifiers (usernames, domains), check for homoglyphs using skeleton forms or confusables databases.
Restrict character sets: For sensitive identifiers, limit to well-defined character sets (ASCII alphanumeric preferred).
Apply consistently: Use identical Unicode processing (normalization, stripping, filtering) throughout application (input, storage, comparison, display).
Related Security Patterns & Anti-Patterns
Encoding Bypass Anti-Pattern: Unicode issues are a specific type of encoding manipulation that can bypass security filters.
1---2name: unicode-security-anti-pattern3description: Security anti-pattern for Unicode-related vulnerabilities (CWE-176). Use when generating or reviewing code that handles usernames, displays text, validates input, or compares strings. Detects confusable characters, normalization issues, and bidirectional text attacks.4---56# Unicode Security Anti-Pattern78**Severity:** Medium910## Summary1112Applications fail to handle Unicode character representation variants, enabling username spoofing, phishing, and validation bypasses through:13141. **Confusable Characters (Homoglyphs):** Identical-looking characters from different scripts (Latin 'a' vs. Cyrillic 'а').152. **Normalization Issues:** Multiple byte sequences for the same character (precomposed vs. base + combining accent).163. **Zero-Width Characters:** Non-printing characters hiding malicious content or altering string lengths.174. **Bidirectional Text Overrides:** Control characters reordering display (obfuscating `exe.pdf` as `fdp.exe`).1819## The Anti-Pattern2021The anti-pattern is processing Unicode strings without normalization, confusable detection, or control character stripping.2223### BAD Code Example2425```python26# VULNERABLE: Comparing strings without normalization or confusable detection.27def authenticate_user(provided_username, password):28 # This example assumes `fetch_user_from_db` expects the exact string from the DB.29 stored_user = fetch_user_from_db(provided_username)3031 if stored_user and stored_user.password == hash_password(password):32 # The 'admin' account exists.33 # An attacker registers an account with username "аdmin" (Cyrillic 'a').34 # The database stores "аdmin".35 # When an attacker tries to log in as "аdmin", `provided_username` is "аdmin".36 # `fetch_user_from_db` finds the attacker's "аdmin" user.37 # But if the application internally processes "аdmin" to "admin"38 # in some other place for a check like `if username == "admin"`,39 # then "аdmin" might bypass this check.40 # Or, more directly, if `fetch_user_from_db` is case/normalization insensitive:41 # Attacker registers "Admin" (Latin A).42 # Legitimate user is "admin" (Latin a).43 # Both may resolve to the same internal user, or one user can spoof another.44 return True45 return False4647# Another example: Allowing confusable characters for domain names.48# Attacker registers "pаypal.com" (with Cyrillic 'a')49# This looks identical to "paypal.com" (with Latin 'a'), enabling phishing.50```5152### GOOD Code Example5354```python55# SECURE: Normalize, filter, and compare Unicode strings consistently.56import unicodedata57import re5859def normalize_and_sanitize_username(username):60 # 1. Normalize to canonical form (NFC) for consistent comparison.61 # NFC replaces combining characters with precomposed equivalents.62 normalized = unicodedata.normalize('NFC', username)6364 # 2. Strip zero-width and bidirectional control characters.65 # Prevents display manipulation and hidden content.66 sanitized = re.sub(r'[\u200B-\u200F\u202A-\u202E\u2066-\u2069]', '', normalized)6768 # 3. Apply confusable detection (recommended for critical identifiers).69 # Convert to "skeleton" form or use confusables database.70 # (Implementation depends on specific libraries/algorithms).7172 # 4. Enforce allowlist of permitted characters.73 # For usernames, restrict to ASCII alphanumeric and limited symbols.74 if not re.fullmatch(r'^[a-zA-Z0-9_.-]+$', sanitized):75 raise ValueError("Username contains disallowed characters.")7677 return sanitized7879def authenticate_user_secure(provided_username, password):80 # All usernames should be normalized and sanitized consistently before storage and comparison.81 sanitized_username = normalize_and_sanitize_username(provided_username)82 stored_user = fetch_user_from_db(sanitized_username)8384 if stored_user and stored_user.password == hash_password(password):85 return True86 return False8788# When displaying usernames or domain names, consider using Punycode for internationalized domain names (IDNs)89# to make spoofing more obvious to users.90```9192## Detection9394- **Review string comparisons:** Look for any comparisons of user-controlled strings, especially for authentication, authorization, or access control decisions.95- **Check input processing:** See how input strings are handled from reception to storage and display. Are normalization steps applied consistently?96- **Test with confusable characters:** Try registering usernames or domains that use homoglyphs (e.g., Cyrillic 'a' instead of Latin 'a') for common reserved names (admin, root) or well-known brands (paypal, apple).97- **Test with zero-width characters:** Insert zero-width characters (e.g., `\u200B`) into inputs to see if they bypass length checks or string comparisons.9899## Prevention100101- [ ] **Normalize all Unicode input:** Convert all strings to NFC (Normalization Form C) before validation, storage, or comparison.102- [ ] **Strip dangerous control characters:** Remove zero-width spaces (`\u200B`), bidirectional overrides (`\u202E`), and non-printing characters.103- [ ] **Implement confusable detection:** For critical identifiers (usernames, domains), check for homoglyphs using skeleton forms or confusables databases.104- [ ] **Restrict character sets:** For sensitive identifiers, limit to well-defined character sets (ASCII alphanumeric preferred).105- [ ] **Apply consistently:** Use identical Unicode processing (normalization, stripping, filtering) throughout application (input, storage, comparison, display).106107## Related Security Patterns & Anti-Patterns108109- [Encoding Bypass Anti-Pattern](../encoding-bypass/): Unicode issues are a specific type of encoding manipulation that can bypass security filters.110- [Missing Input Validation Anti-Pattern](../missing-input-validation/): Failure to handle Unicode correctly is a form of improper input validation.111- [Cross-Site Scripting (XSS) Anti-Pattern](../xss/): Malicious Unicode characters can sometimes be used to bypass XSS filters.112113## References114115- [OWASP Top 10 A05:2025 - Injection](https://owasp.org/Top10/2025/A05_2025-Injection/)116- [OWASP GenAI LLM05:2025 - Improper Output Handling](https://genai.owasp.org/llmrisk/llm05-improper-output-handling/)117- [OWASP API Security API8:2023 - Security Misconfiguration](https://owasp.org/API-Security/editions/2023/en/0xa8-security-misconfiguration/)118- [CWE-176: Improper Handling of Unicode](https://cwe.mitre.org/data/definitions/176.html)119- [CAPEC-71: Using Unicode Encoding to Bypass Validation](https://capec.mitre.org/data/definitions/71.html)120- [Unicode Security Considerations](https://unicode.org/reports/tr36/)121- [Unicode Confusables](https://util.unicode.org/UnicodeJsps/confusables.jsp)122- Source: [sec-context](https://github.com/Arcanum-Sec/sec-context)
Run npx skillmds@latest add igbuend/unicode-security-anti-pattern in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Security anti-pattern for Unicode-related vulnerabilities (CWE-176). Use when generating or reviewing code that handles usernames, displays text, validates input, or compares strings. Detects confusable characters, normalization issues, and bidirectional text attacks. It is listed under Security on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
igbuend (@igbuend) published this skill. Their other Agent Skills are listed on their SkillMD profile.