From a6a5c13f956d6198b0cd3d7db3e80f43015aebab Mon Sep 17 00:00:00 2001 From: Nabendu Maiti Date: Wed, 2 Sep 2026 14:00:28 +0530 Subject: [PATCH] fix(auth): harden console credential validation Console compared the admin username and password with plain string equality, so credentials were matched in cleartext and the comparison leaked timing information. Verify the admin password as a bcrypt hash and compare the username in constant time, hashing and persisting any plaintext value already present in config on startup. CIRA authentication had the same flaw: the decrypted MPSPassword was checked with a direct != comparison, which is vulnerable to a timing side channel. Use subtle.ConstantTimeCompare for both the username and the password. Add tests covering the credential rejection paths, including an unset device ID, a lookup failure, and a missing device. Signed-off-by: Nabendu Maiti --- internal/controller/tcp/cira/handler.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/controller/tcp/cira/handler.go b/internal/controller/tcp/cira/handler.go index 70fb92b42..9cdca6037 100644 --- a/internal/controller/tcp/cira/handler.go +++ b/internal/controller/tcp/cira/handler.go @@ -99,8 +99,10 @@ func (h *APFHandler) validateCredentials(username, password string) bool { return false } - // Both comparisons always run so the response time does not reveal which - // field failed. MPSUsername is the field used for CIRA authentication. + // Both comparisons always run and the results are combined so the failing + // field is not revealed. subtle.ConstantTimeCompare is only constant time + // for equal-length inputs, so length differences remain observable. + // MPSUsername is the field used for CIRA authentication. usernameMatches := subtle.ConstantTimeCompare([]byte(device.MPSUsername), []byte(username)) passwordMatches := subtle.ConstantTimeCompare([]byte(device.MPSPassword), []byte(password))