The Claim That Rewrote Itself on Every Sign-In
undefined !== false — how a missing claim turned every non-staff login into a writes storm
Published Nov 5, 2026
Executive Summary
A profile bootstrap compared a token claim against a computed staff flag with strict inequality. A non-staff account carries no such claim at all, so `undefined !== false` was always true: every sign-in rewrote custom claims, forced a token refresh, and logged a false security event. The fix treats an absent claim as 'not staff' explicitly and pins all four states with unit tests.
The Prompt
“Add a bootstrapProfile callable that keeps the user's staff flag in sync with their custom claims on sign-in.”
The Flaw
The sync compared the raw token claim against the computed staff flag with strict inequality. But a non-staff account has no `staff` claim at all — the token simply doesn't carry it — so the left side is `undefined`, not `false`. `undefined !== false` is always `true`, so every non-staff sign-in 'detected' a change that never happened: it rewrote the custom claims, forced a client token refresh, and wrote a `staff_claim_cleared` security event. The code treated 'no claim' and 'claim is false' as the same thing; JavaScript doesn't.
Risk & Impact
[Correctness] Every non-staff sign-in rewrote the custom claims, forced a token refresh, and logged a false security event — 17 events for 3 users in 10 minutes.
Cost: False security events flood the audit log and would eventually drown out a real `staff_claim_cleared` (an actual privilege removal).
Code Comparison
❌ Vulnerable (AI generated)
❌ Vulnerable (AI generated)
✅ Hardened (production ready)
The Fix
Treat an absent claim as 'not staff' explicitly via a helper, and pin all four states (absent / undefined / true / false) with unit tests.
Prevention
- [manual] At any auth or trust boundary, decide explicitly what an absent value means — never rely on `undefined !== false` to work out.
- [ci] Require unit tests that cover absent and undefined claim states for any code that writes custom claims or security events.
Why This Happened
Root cause: the prompt omitted Rule 4 and Rule 11 — the guardrails that would have caught this. See the full guide →
