fix(storage): reject case-folded home directory collisions

GetByScope compared scopes with a case-sensitive exact match, so on a
case-insensitive filesystem two accounts whose scopes differ only in case
(e.g. /users/CaseVictim and /users/casevictim) were treated as distinct even
though both resolve to the same physical home directory. The second account
could then read, overwrite and delete the first account's files.

Match the scope case-insensitively so the collision check performed during
signup, proxy and hook provisioning catches these aliases and rejects the
duplicate.

Refs GHSA-576v-w77m-gr84
This commit is contained in:
Henrique Dias 2026-07-25 07:55:50 +02:00
parent 8ddd3d1db9
commit 4b8a8d72ce
No known key found for this signature in database

View file

@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"reflect"
"regexp"
"github.com/asdine/storm/v3"
"github.com/asdine/storm/v3/q"
@ -44,7 +45,11 @@ func (st usersBackend) GetBy(i interface{}) (user *users.User, err error) {
func (st usersBackend) GetByScope(scope string) (*users.User, error) {
user := &users.User{}
err := st.db.Select(q.Eq("Scope", scope)).First(user)
// Match case-insensitively: on a case-insensitive filesystem two scopes
// that differ only in case (e.g. /users/Alice and /users/alice) resolve to
// the same home directory, so they must be treated as a collision.
pattern := "(?i)^" + regexp.QuoteMeta(scope) + "$"
err := st.db.Select(q.Re("Scope", pattern)).First(user)
if err != nil {
if errors.Is(err, storm.ErrNotFound) {
return nil, fberrors.ErrNotExist