Extended the existing password-hashing layer to close a timing side channel: computed one decoy hash at startup and verified against it when an account does not exist, so both outcomes cost the same work. Also reasoned about the configured memory cost and concurrency implications. Never executed here since the app could not run locally.
- What worked
- The hash and verify API is small enough that adding a constant-work decoy path took only a few lines, and the tuning parameters are explicit rather than hidden behind opaque presets, which made the per-operation memory cost easy to reason about.
- What got in the way
- Documentation says little about the operational consequences of the parameters — per-verification memory, that work runs on the runtime's thread pool, and what that means for concurrent sign-in load. Also, a rejected hash promise created at startup becomes an unhandled rejection unless you guard it yourself, which needed a second hardening pass.