Skip to main content

Authentication Timing Side-Channel

ClassCWE-208, Observable Timing Discrepancy
StatusExploitable signal mitigated; sub-millisecond residual accepted
Tracked asGHSA-7rw5-9f7q-xj36

Summary

The sign-in endpoint took measurably longer for an existing account than for a non-existent one, because the password hash was only verified when the account existed. The difference (roughly 150 ms versus 5 ms) let an attacker enumerate valid accounts by response time, which can seed a later password-spraying attack.

What we mitigated

This was reported to us and is tracked as GHSA-7rw5-9f7q-xj36. We addressed it in the sign-in path: authentication now performs a bcrypt comparison against a fixed dummy hash on the paths where no user, or no active credential, is found. Every sign-in attempt therefore pays the same bcrypt cost regardless of whether the account exists.

Measured, this collapses the difference from roughly 20-40x down to about 1.03x (bcrypt against bcrypt), which is indistinguishable from ordinary run-to-run variance. The practically exploitable enumeration oracle is removed.

The millisecond residual that remains is accepted as-is. This is not a matter of further effort: what is left is bounded by physical and runtime limits (the database, the Python interpreter, CPU behaviour, operating-system scheduling and network jitter) rather than by anything Open WebUI's code can improve, so there is little more to be gained from here. The reasoning is set out in What remains, and why it is not fixable below.

What remains, and why it is not fixable

Perfectly constant-time authentication is not achievable in application-level code. Once the dominant bcrypt cost is equalised, the remaining differences are sub-millisecond and are produced by layers Open WebUI does not control:

  • the database lookup (an index hit for an existing email versus a miss for a non-existent one),
  • the Python interpreter and garbage collector,
  • CPU behaviour: cache state, branch prediction, speculative execution,
  • operating-system scheduling,
  • network jitter.

None of these can be driven to exactly zero from Python. Fixed-time padding ("always respond within a set window") does not close it either: the window has to be padded to the worst case, which penalises every legitimate login, and a sufficiently precise attacker can still observe the work done before the padding begins. It trades a real, permanent cost for a false sense of completeness.

The residual signal is sub-millisecond and dominated by network and scheduling noise, so it is not reliably measurable across a network. We therefore accept it as a known limitation rather than a remediable defect.

References

This content is for informational purposes only and does not constitute a warranty, guarantee, or contractual commitment. Open WebUI is provided "as is." See your license for applicable terms.