The 6-digit EmailVerificationCode that gates pre-registration email ownership is generated with System.Random, in an identical GenerateNumericCode helper duplicated across two handlers:
InitiatePreRegistrationCommandHandler.cs:372 (new Random() → random.Next(0,10) per digit)
System.Random is seeded from the system clock and is not cryptographically secure. An attacker who can estimate when a code was issued (registration time is often observable) can reconstruct the seed and predict the code, taking over a pre-registration / verifying an email they don't control. The code space is only 10^6, and while VerifyPreRegistrationEmail does increment EmailVerificationAttempts, prediction defeats any attempt cap (correct on the first try).
Fix
Generate codes with a CSPRNG. Added SpikerSoft.Business.Domain.Authentication.SecureNumericCode.Generate(length) using RandomNumberGenerator.GetInt32(0, 10) per digit, and routed both handlers through it (removed the duplicated new Random() helpers).
Verification
SecureNumericCodeTests: correct length, digits-only, low collision rate across many draws (a clock-seeded new Random() in a tight loop can repeat), non-positive length throws. Build clean.
Related (not in this PR — flagging for the team)
InitiatePreRegistrationCommandHandler.HashPassword stores an unsalted SHA-256 of the password in the pre-reg record ("temporary before Keycloak"). If that store leaks, those are rainbow-table-crackable. Worth deciding whether the password should be stored at all pre-Keycloak, or via a proper salted KDF — likely belongs with #404.
Confirm EmailVerificationAttempts is actually enforced (rejects after N), not just incremented/logged.
Resolved by spikersoft-backend PR (linked below).
## Problem
The 6-digit `EmailVerificationCode` that gates pre-registration email ownership is generated with **`System.Random`**, in an identical `GenerateNumericCode` helper duplicated across two handlers:
- `InitiatePreRegistrationCommandHandler.cs:372` (`new Random()` → `random.Next(0,10)` per digit)
- `ResendPreRegistrationCodesCommandHandler.cs:556` (same)
`System.Random` is seeded from the system clock and is **not** cryptographically secure. An attacker who can estimate *when* a code was issued (registration time is often observable) can reconstruct the seed and **predict the code**, taking over a pre-registration / verifying an email they don't control. The code space is only 10^6, and while `VerifyPreRegistrationEmail` does increment `EmailVerificationAttempts`, prediction defeats any attempt cap (correct on the first try).
## Fix
Generate codes with a CSPRNG. Added `SpikerSoft.Business.Domain.Authentication.SecureNumericCode.Generate(length)` using `RandomNumberGenerator.GetInt32(0, 10)` per digit, and routed both handlers through it (removed the duplicated `new Random()` helpers).
## Verification
`SecureNumericCodeTests`: correct length, digits-only, low collision rate across many draws (a clock-seeded `new Random()` in a tight loop can repeat), non-positive length throws. Build clean.
## Related (not in this PR — flagging for the team)
- `InitiatePreRegistrationCommandHandler.HashPassword` stores an **unsalted SHA-256** of the password in the pre-reg record ("temporary before Keycloak"). If that store leaks, those are rainbow-table-crackable. Worth deciding whether the password should be stored at all pre-Keycloak, or via a proper salted KDF — likely belongs with #404.
- Confirm `EmailVerificationAttempts` is actually **enforced** (rejects after N), not just incremented/logged.
Resolved by spikersoft-backend PR (linked below).
Resolved in spikersoft-backend PR #130 (merged to master as 389f570). Pre-registration verification codes now use SecureNumericCode (CSPRNG via RandomNumberGenerator) in both Initiate + Resend handlers. 5/5 tests. Closing.
Two adjacent items remain flagged here for the team (not in this PR): the unsalted-SHA-256 HashPassword pre-Keycloak (belongs with #404), and confirming EmailVerificationAttempts is enforced vs. only incremented — reopen/track separately if you want those actioned.
Resolved in spikersoft-backend PR #130 (merged to `master` as `389f570`). Pre-registration verification codes now use `SecureNumericCode` (CSPRNG via `RandomNumberGenerator`) in both Initiate + Resend handlers. 5/5 tests. Closing.
Two adjacent items remain flagged here for the team (not in this PR): the unsalted-SHA-256 `HashPassword` pre-Keycloak (belongs with #404), and confirming `EmailVerificationAttempts` is enforced vs. only incremented — reopen/track separately if you want those actioned.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
The 6-digit
EmailVerificationCodethat gates pre-registration email ownership is generated withSystem.Random, in an identicalGenerateNumericCodehelper duplicated across two handlers:InitiatePreRegistrationCommandHandler.cs:372(new Random()→random.Next(0,10)per digit)ResendPreRegistrationCodesCommandHandler.cs:556(same)System.Randomis seeded from the system clock and is not cryptographically secure. An attacker who can estimate when a code was issued (registration time is often observable) can reconstruct the seed and predict the code, taking over a pre-registration / verifying an email they don't control. The code space is only 10^6, and whileVerifyPreRegistrationEmaildoes incrementEmailVerificationAttempts, prediction defeats any attempt cap (correct on the first try).Fix
Generate codes with a CSPRNG. Added
SpikerSoft.Business.Domain.Authentication.SecureNumericCode.Generate(length)usingRandomNumberGenerator.GetInt32(0, 10)per digit, and routed both handlers through it (removed the duplicatednew Random()helpers).Verification
SecureNumericCodeTests: correct length, digits-only, low collision rate across many draws (a clock-seedednew Random()in a tight loop can repeat), non-positive length throws. Build clean.Related (not in this PR — flagging for the team)
InitiatePreRegistrationCommandHandler.HashPasswordstores an unsalted SHA-256 of the password in the pre-reg record ("temporary before Keycloak"). If that store leaks, those are rainbow-table-crackable. Worth deciding whether the password should be stored at all pre-Keycloak, or via a proper salted KDF — likely belongs with #404.EmailVerificationAttemptsis actually enforced (rejects after N), not just incremented/logged.Resolved by spikersoft-backend PR (linked below).
Resolved in spikersoft-backend PR #130 (merged to
masteras389f570). Pre-registration verification codes now useSecureNumericCode(CSPRNG viaRandomNumberGenerator) in both Initiate + Resend handlers. 5/5 tests. Closing.Two adjacent items remain flagged here for the team (not in this PR): the unsalted-SHA-256
HashPasswordpre-Keycloak (belongs with #404), and confirmingEmailVerificationAttemptsis enforced vs. only incremented — reopen/track separately if you want those actioned.