# Case normalization mismatch in auth lookup

Auth or ACL lookups that case-fold or normalize keys differently than the resource or rule is stored

_Area: Web access control and IDOR. Seen in 3 disclosed reports; latest 2026-08-05. Updated 2026-10-24._

## How it works

A security decision compares a normalized key against a stored key, but the two sides are normalized differently. The lookup folds case (or strips characters, or expands wildcards) while the stored rule, TLS config, or scope directory keeps its original form. The comparison silently misses, and the code falls back to a default that is less restrictive than the intended rule. The assumption that breaks: that "the same name" means the same bytes on both sides of the check. Any lossy or one-sided normalization turns a match into a miss, and a miss into a bypass.

## Where it shows up

- Handshake-time selection (SNI, TLS config maps) matched by exact key while request-time routing matches case-insensitively or by wildcard [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-9cr8-q42q-g8m7.md)
- Patches that fold only the lookup path and leave compiled rule keys verbatim [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-hxvh-4h3w-prp9.md)
- Sanitizers that map many inputs to one output (strip `..`, replace disallowed chars) with no uniqueness check on the derived scope [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-7rc3-g7h6-22m7.md)
- Map lookups on user-controlled hostnames, paths, or usernames where the map was built from config or signup input
- Fallback branches that apply a default policy when no key matches

## How to review

Find every place an auth, ACL, tenant, or TLS decision is keyed by a user-controlled string. For each, confirm the exact property: both operands are normalized by the same function before comparison, and the normalization is injective over the input space that reaches the check. Check wildcard and mixed-case keys explicitly. For derived scopes (home dirs, buckets, tenants), verify a uniqueness check exists at creation time, not just a sanitizer. Trace the no-match path: does it deny, or fall back to a permissive default? A safe design denies on miss and rejects colliding keys at write time.

## Variants seen

- TLS config selected by exact case-sensitive SNI map lookup; wildcard/mixed-case hosts fall back to default config lacking mTLS [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-9cr8-q42q-g8m7.md)
- Route-rule lookup lowercased but rule keys left verbatim, dropping appMiddleware gates [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-hxvh-4h3w-prp9.md)
- Many-to-one username sanitizer with no scope-uniqueness check, giving two users one home directory [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-7rc3-g7h6-22m7.md)

## Why fixes fail

E2 is an incomplete fix for an earlier CVE: the patch normalized only the lookup side, so the asymmetry remained and mixed-case keys still bypassed. Folding one operand is not a fix.

## Impact and severity

Unauthenticated access to protected pages [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-hxvh-4h3w-prp9.md), skipped client-certificate enforcement on HTTP/3 [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-9cr8-q42q-g8m7.md), and cross-user file read/overwrite [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-7rc3-g7h6-22m7.md). Severity rises when the miss falls back to a default that disables auth, and when the mismatched layer is a transport handshake the request layer never re-checks.

## Evidence
- E1: https://bugbountylibrary.com/r/ghsa%3AGHSA-9cr8-q42q-g8m7.md
- E2: https://bugbountylibrary.com/r/ghsa%3AGHSA-hxvh-4h3w-prp9.md
- E3: https://bugbountylibrary.com/r/ghsa%3AGHSA-7rc3-g7h6-22m7.md
