Proxy auth trusts an unvalidated header and auto-provisions an empty-username account when the header is absent
low Possibly Valid medium confidence
Status
Possibly Valid
The missing non-empty username guard and the auto-provisioning path are confirmed in source; reachability depends on the proxy-auth deployment and whether a header-less request can reach the handler, so possibly_valid.
Repository / Component
Plain-English Description
Under proxy authentication the app takes the username straight from a request header without checking it is present or non-blank. If the header is missing, it can auto-create a blank-named account with default permissions and hand out a login token for it.
Description of the Underlying Issue
ProxyAuth.Auth (auth/proxy.go:22-66) reads username := r.Header.Get(a.Header) (auth/proxy.go:22) with no check that the value is non-empty or non-whitespace. It then looks the user up via storage GetBy(username); for an empty string BoltDB's GetBy (storage/bolt/users.go:19-42) returns ErrNotFound, which surfaces as ErrNotExist. In ProxyAuth.Auth (auth/proxy.go:24-25) ErrNotExist drives createUser(username=''), which persists a new user (with Admin/Execute forced to false) and returns it for token minting (auth/proxy.go:43-63). The only guard against an empty username is in settings/dir.go: MakeUserDir rejects an empty username only when userScope=='' AND CreateUserDir is set (settings/dir.go:24-31); otherwise (settings/dir.go:33) it anchors the scope to root and the empty-username account is created. Thus, absent that specific CreateUserDir + empty-default-scope combination, a header-less request auto-provisions a degenerate empty-username user with default permissions and receives a valid token, which is then reused on subsequent header-less requests.
Potential Attack
On a deployment configured for auth method 'proxy', a request reaches the auth handler without the configured trusted header — via a proxy misconfiguration, a passthrough/bypass route, or direct access to the backend that skips the fronting proxy. ProxyAuth.Auth reads an empty username, GetBy('') returns ErrNotExist, and createUser('') persists a new empty-username account and mints a token for it. Unless the CreateUserDir + empty-default-scope guard fires, the attacker obtains an authenticated session under a degenerate account.
Outcomes of Potential Attack
The attacker gains an authenticated session (JWT) as an auto-provisioned empty-username, non-admin user carrying the configured default permissions and default scope. Depending on those defaults, this yields unintended access to the file browser as a persisted account that recurs for every header-less request. The account is created without any legitimate proxy having asserted an identity, undermining the proxy-auth trust assumption.
Affected Scope
ProxyAuth.Auth / createUser (auth/proxy.go:22-66)
Suggested Fix (plain english)
Reject requests whose proxy-auth header is missing, empty, or whitespace before looking up or creating any user.
Suggested Fix (detailed)
In ProxyAuth.Auth, immediately after reading username := r.Header.Get(a.Header) (auth/proxy.go:22), reject the request (e.g. return os.ErrPermission / an auth error) when the trimmed username is empty, before calling usr.Get / createUser. Do not rely on MakeUserDir's narrow empty-username guard (settings/dir.go:24-31) as the sole defense. Document that proxy auth requires the fronting proxy to always set the header and to block direct backend access. Verify by sending a request without the configured header and confirming it is rejected and that no empty-username account is created in storage. Reject empty/whitespace usernames in ProxyAuth.Auth before usr.Get/createUser.
Validation
The missing non-empty username guard and the auto-provisioning path are confirmed in source; reachability depends on the proxy-auth deployment and whether a header-less request can reach the handler, so possibly_valid.
Full Evidence
auth/proxy.go:22 username := r.Header.Get(a.Header) with no non-empty/whitespace validation
storage/bolt/users.go:19-42 GetBy(string) looks up by Username; an empty string yields ErrNotFound -> ErrNotExist
auth/proxy.go:24-25 ErrNotExist drives createUser(username='')
settings/dir.go:24-31 empty-username rejection only fires when userScope=='' && CreateUserDir; :33 otherwise anchors to root
auth/proxy.go:43-63 createUser persists the user (Admin/Execute forced false) and returns it for token minting
Proven fact: A blank/absent proxy header leads ProxyAuth to auto-provision an empty-username user with default permissions, unless the CreateUserDir + empty-default-scope combination forces MakeUserDir to reject the empty username
Unvalidated fact: Requires auth method = 'proxy' AND a request without the configured header reaching the handler (proxy bypass, passthrough, or misconfiguration)
Unvalidated fact: Whether the resulting empty-username user's default scope grants meaningful access is deployment-dependent
auth/proxy.go:22 — username := r.Header.Get(a.Header) with no non-empty / non-whitespace validation
storage/bolt/users.go:19-42 — GetBy(string) looks up by Username; an empty string yields ErrNotFound -> surfaced as ErrNotExist
auth/proxy.go:24-25 — ErrNotExist branch drives createUser(username='')
auth/proxy.go:43-63 — createUser persists the user (Admin/Execute forced false) and returns it for token minting
settings/dir.go:24-31 — empty-username rejection fires only when userScope=='' && CreateUserDir; settings/dir.go:33 otherwise anchors to root and proceeds
Proven fact: ProxyAuth.Auth applies no non-empty/whitespace validation to the header-derived username before lookup and auto-provisioning.
Proven fact: An empty username produces ErrNotExist, which routes into createUser('') and persists a new default-permission, non-admin account, returning it for token minting.Proven fact: The empty-username account is blocked only by the narrow CreateUserDir + empty-default-scope combination in MakeUserDir; otherwise creation proceeds.
Unvalidated fact: Exploitation requires auth method = 'proxy' AND a request without the configured header actually reaching ProxyAuth.Auth (proxy bypass, passthrough route, or misconfiguration).
Unvalidated fact: Whether the auto-provisioned empty-username user's default scope and permissions grant meaningful access is deployment-dependent.
auth/proxy.go:22-25 username := r.Header.Get(a.Header) with no non-empty/whitespace validation before usr.Get/createUser
storage/bolt/users.go:18-40 GetBy: a string argument is looked up by Username; empty string -> storm ErrNotFound -> ErrNotExist, driving createUser
settings/dir.go:24-31 empty-username rejection only executes when userScope=="" && CreateUserDir; :33 path.Join("/",userScope) otherwise anchors to root