Great work!

XP to next level

BugEater

Case-Sensitivity

Intermediate Manual QA 46 XP 57 min

Updated: 05/07/2026

Start Module

Two Systems, Same String, Different Answer

A user registers with username Admin. Later they try to register again with admin. The system rejects it: "Username already taken." Good — the system normalizes usernames to lowercase and prevents collisions.

Three months later, a security engineer finds that ADMIN and Admin return the same profile page, but the password check for Admin only matches when you submit the original case. The normalization happens inconsistently: at registration it lowercases, at login it does not.

Case-sensitivity bugs are rarely about the case itself. They are about inconsistent application of a policy across different code paths.

The Two Fundamental Questions

Before testing any text field for case sensitivity, answer two questions:

  1. Is this field case-sensitive by design? Passwords yes. Usernames — depends on the application. Email addresses — per RFC, the local part is technically case-sensitive, but virtually all mail systems treat them as case-insensitive. Enum values and status codes — usually case-sensitive.

  2. Is the policy applied consistently? If usernames are case-insensitive, does that apply equally to registration, login, password reset, profile lookup, admin search, API endpoints, and error messages? Every code path that touches the field is a separate opportunity for the policy to be violated.

Database-Level Case Sensitivity

The database is often the source of truth for case sensitivity, and it has its own rules:

PostgreSQL: String comparisons are case-sensitive by default. WHERE username = 'Admin' does NOT match admin. To get case-insensitive comparison, you must use ILIKE, LOWER(username) = LOWER(?), or a citext column type.

MySQL: The default collation is utf8mb4_general_ci (case-insensitive). WHERE username = 'Admin' matches admin, ADMIN, and admin. Switching to a _cs (case-sensitive) collation changes this.

SQLite: LIKE is case-insensitive for ASCII characters only. For Unicode characters, it is case-sensitive. WHERE username LIKE 'admin' matches ADMIN but does NOT match АDMIN (where the A is Cyrillic).

This means the same SQL query behaves differently across databases. A test that passes on SQLite (which the team uses for local testing) may fail on PostgreSQL (production).

Case Sensitivity in Security

Case sensitivity is not just a data integrity question — it is a security question in several specific scenarios:

Token validation: Password reset tokens, email verification links, and session tokens should be case-sensitive. A case-insensitive comparison doubles (or more) the chance of a brute-force match succeeding by allowing partial matches.

Permission checks: A role check that compares userRole.equals("ADMIN") case-sensitively will fail silently if the database stores admin. The user gets no access and no error — they simply cannot perform admin actions. No one files a bug because they assume it is working as designed.

Username enumeration: If the login endpoint normalizes case but the "forgot password" endpoint does not, an attacker can probe whether Admin is a registered user by submitting to forgot-password and observing whether the response says "email not found" (case-sensitive miss) vs. "check your inbox" (case-insensitive hit in login flow).

Testing for Case Sensitivity

For any text field that might have case policies, your test matrix should cover:

Test Input Expected
Exact case Original case from registration Works
All uppercase ADMIN (if registered as admin) Pass or fail?
All lowercase admin (if registered as Admin) Pass or fail?
Mixed case AdMiN Pass or fail?
First letter capitalized Admin vs admin Consistent with others?

Then repeat the matrix across all code paths that accept the same value: registration, login, profile retrieval, search, admin panel, API. The bug is almost always that one path behaves differently from the others.

Unicode Case Folding

Basic case-sensitivity testing covers A-Z / a-z. But Unicode has case mappings for hundreds of other scripts. Some examples that break naive case conversion:

  • Turkish i (U+0069) → uppercase İ (U+0130, dotted capital I), not I (U+0049). The reverse: Turkish I → lowercase ı (U+0131, dotless i). A toUpperCase() that ignores locale converts Turkish input to INPUT, but a Turkish locale converts it to İNPUT.
  • German ß (U+00DF, sharp s) → uppercase is SS (two characters). A length limit of 10 characters might allow straße (6 chars) but reject its uppercase STRASSE (7 chars).
  • Greek Σ (sigma) has two lowercase forms: σ (mid-word) and ς (end-of-word). Case-folding must be context-aware to be correct.

For applications with international users, naive toLowerCase() / toUpperCase() is a latent bug. Locale-sensitive case conversion is required for correctness.

Challenge: Restore Password

The practice challenge for this module is the Restore Password flow. As you test it, pay attention not just to whether the form accepts or rejects your input, but to whether the case behavior is consistent across the different steps of the flow.

Ask yourself: if I register with one case and attempt to restore with another, does the system treat them as the same user? Does the behavior match what a user would expect?

Module content