Three CubeOps store getters turn any database error into ("", nil) whenever the scanned value is
empty, so a database outage is reported to the operator as 401 invalid credentials instead of a
500.
Environment
- CubeSandbox version / commit:
5960c56 (master)
- Host OS and kernel version: any
- KVM info (
modinfo kvm): n/a — control-plane only
- Deployment mode: single-node / cluster
- Relevant component: CubeOps
Steps to Reproduce
- Bring up CubeOps against MySQL/Postgres and confirm you can log in.
- Stop the database (or block the port) so reads fail:
docker stop <mysql-container>
curl -si -X POST http://<cubeops>/api/v1/auth/login -d '{"username":"admin","password":"admin"}'
Expected Behavior
500 with the underlying database error, so the operator can see the real fault. The comment in
AuthService.Login states exactly this intent: "Genuine infrastructure errors (DB down, etc.) are
still returned verbatim so the operator can diagnose them."
Actual Behavior
401 {"error":"invalid credentials"}. The operator chases a phantom credentials problem while the
real fault is the database.
Reproduced with verbatim copies of both functions:
DB unreachable -> pwd="" err=<nil> (the connection error is DISCARDED)
user not found -> pwd="" err=<nil>
ctx canceled -> pwd="" err=<nil> (discarded)
Login: err==nil, so BOTH branches of `if err != nil` are skipped -> DEAD CODE
Login: -> ErrInvalidCredentials (401) even though the DATABASE IS DOWN
Additional Context
The pattern appears three times:
CubeOps/internal/store/setting.go:24 — GetSystemSetting
CubeOps/internal/store/setting.go:59 — GetSetting
CubeOps/internal/store/setting.go:111 — GetUserPassword
if errors.Is(err, sql.ErrNoRows) || val == "" {
return "", nil // row missing, value empty, AND any DB error
}
return val, err
val is the zero value whenever the query fails, so val == "" is true for a genuine error too.
Because of this, both branches of if err != nil in AuthService.Login
(internal/service/auth.go:82-91) are unreachable — control falls straight through to
VerifyPassword("", password). ChangePassword (:179-185) has the same shape.
There is also a contract mismatch that let this survive: the service-layer UserStore fake in
internal/service/auth_test.go:23-29 returns ("", errors.New("user not found")) for an unknown
user, i.e. it models not-found as an error, while the real store returns ("", nil). The tests
therefore passed against a contract production never implemented.
staticcheck does not flag this (the assignment is used), and go vet does not either — it needs a
reader to notice the shadow-free-but-dead branch.
Related: #1 (empty JWT signing secret) is the same root cause reaching a different consumer —
GetOrCreateSystemSetting reads back through GetSystemSetting, so a transient read error yields an
empty JWT secret with no error.
Three CubeOps store getters turn any database error into
("", nil)whenever the scanned value isempty, so a database outage is reported to the operator as
401 invalid credentialsinstead of a500.Environment
5960c56(master)modinfo kvm): n/a — control-plane onlySteps to Reproduce
docker stop <mysql-container>curl -si -X POST http://<cubeops>/api/v1/auth/login -d '{"username":"admin","password":"admin"}'Expected Behavior
500with the underlying database error, so the operator can see the real fault. The comment inAuthService.Loginstates exactly this intent: "Genuine infrastructure errors (DB down, etc.) arestill returned verbatim so the operator can diagnose them."
Actual Behavior
401 {"error":"invalid credentials"}. The operator chases a phantom credentials problem while thereal fault is the database.
Reproduced with verbatim copies of both functions:
Additional Context
The pattern appears three times:
CubeOps/internal/store/setting.go:24—GetSystemSettingCubeOps/internal/store/setting.go:59—GetSettingCubeOps/internal/store/setting.go:111—GetUserPasswordvalis the zero value whenever the query fails, soval == ""is true for a genuine error too.Because of this, both branches of
if err != nilinAuthService.Login(
internal/service/auth.go:82-91) are unreachable — control falls straight through toVerifyPassword("", password).ChangePassword(:179-185) has the same shape.There is also a contract mismatch that let this survive: the service-layer
UserStorefake ininternal/service/auth_test.go:23-29returns("", errors.New("user not found"))for an unknownuser, i.e. it models not-found as an error, while the real store returns
("", nil). The teststherefore passed against a contract production never implemented.
staticcheckdoes not flag this (the assignment is used), andgo vetdoes not either — it needs areader to notice the shadow-free-but-dead branch.
Related: #1 (empty JWT signing secret) is the same root cause reaching a different consumer —
GetOrCreateSystemSettingreads back throughGetSystemSetting, so a transient read error yields anempty JWT secret with no error.